### USER
你是一位资深的产品经理，对基于FreeSWITCH的呼叫中心系统有着丰富的产品经验。我现在想做一个企业的呼叫中心系统，除了可以实现基本的呼入呼出外，还要实现自动外呼和机器人客服。你可以向我问一些启发式的问题澄清需求，最终生成一份需求文档。

### ASSISTANT
您好！很高兴能帮助您规划这个呼叫中心系统。为了生成一份完整且符合您实际业务场景的需求文档，我需要通过一系列问题来深入了解您的需求。

让我们从最基础的业务背景开始：

---

## 第一轮：业务背景与目标

**1. 关于您的企业和目标客户：**
- 这个呼叫中心系统是给**您自己企业内部使用**，还是作为**SaaS产品对外销售**给其他企业？
- 您的企业/目标客户属于哪个行业？（如：电商、金融、教育、医疗、政务热线等）

**2. 关于规模预期：**
- 初期预计需要支持多少个**坐席（人工客服）**同时工作？
- 预计的**日呼叫量**大概是多少？（呼入+呼出）

**3. 关于核心业务痛点：**
- 您目前为什么想要建设这个系统？是**从零开始建设**，还是**替换现有系统**？
- 如果是替换，现有系统最让您不满意的地方是什么？

---

请先回答这几个基础问题，我会根据您的回答继续深入探讨呼入呼出、自动外呼、机器人客服等具体模块的细节需求。🎯

### USER
1.企业内部使用，企业属于服务行业，2.预计支持 150个坐席同时工作，日呼量50000 ，3.从零开始建设

### ASSISTANT
感谢您的回答！150坐席、日呼叫量5万是一个中大型呼叫中心的规模，服务行业的场景也非常典型。

---

## 第二轮：业务场景细化

**4. 关于服务业务类型：**
- 您的服务行业具体是哪类？（如：家政服务、汽车4S店/维修、物业管理、旅游预订、餐饮连锁、上门维修等）
- 您的客户群体主要是**ToC（个人消费者）**还是**ToB（企业客户）**？

**5. 关于呼入呼出业务占比：**
- 目前预估的**呼入和呼出比例**大概是多少？（如：呼入70%呼出30%）
- 呼入的主要场景是什么？（如：咨询、投诉、预约、售后等）
- 呼出的主要场景是什么？（如：回访、营销推广、催收/催缴、通知提醒等）

**6. 关于自动外呼的核心诉求：**
- 您希望自动外呼解决什么问题？
  - [ ] 提高人工坐席效率（预测式外呼，减少等待）
  - [ ] 完全无人化批量通知（如：短信+语音通知）
  - [ ] 营销筛选（机器人先打，意向客户转人工）
  - [ ] 其他：______

**7. 关于机器人客服的定位：**
- 您希望机器人客服承担什么角色？
  - **呼入场景**：IVR导航、自助查询、常见问题解答、转人工前预处理？
  - **呼出场景**：自动回访、满意度调查、标准化通知？
- 机器人是作为**第一道接待**（先机器人再转人工），还是**人工为主机器人辅助**？

---

请您根据实际业务情况回答，这些信息将帮助我明确系统的功能优先级和架构设计方向。💡

### USER
服务行业类型：道路救援、家政服务，代驾服务，客户群体：ToC，呼入40%呼出60%，呼入的场景：叫服务，咨询、投诉，呼出场景：回访、营销推广、催收/催缴、通知提醒等，关于自动外呼的核心诉求：提高人工坐席效率和完全无人化批量通知；机器人客服的定位：**呼入场景**：IVR导航、常见问题解答、转人工前预处理，呼出场景：标准化通知、自动回访、满意度调查。先机器人再转人工

### ASSISTANT
非常清晰！您的业务场景很典型——**道路救援、家政、代驾**都是对**响应速度要求极高**的服务行业，而且ToC场景意味着客户体验至关重要。

---

## 第三轮：关键业务流程与系统集成

**8. 关于呼入核心流程（叫服务场景）：**
- 当客户来电叫服务时，坐席需要**实时派单**给服务人员（救援师傅、家政阿姨、代驾司机）吗？
- 您是否已有**业务系统/派单系统/工单系统**需要与呼叫中心对接？
  - 如果有，是什么系统？（自研、第三方SaaS如：易企秀、工单宝等）
  - 如果没有，是否需要呼叫中心系统**内置工单模块**？

**9. 关于客户来电识别：**
- 客户来电时，您希望系统能自动**识别客户身份**吗？
  - [ ] 通过来电号码匹配历史客户资料
  - [ ] 弹出历史服务记录、订单记录
  - [ ] 显示客户标签（VIP、黑名单、投诉倾向等）
- 这意味着系统需要对接您的**CRM或会员系统**，您目前有这类系统吗？

**10. 关于服务工作时间：**
- 您的呼叫中心是**7×24小时**运营，还是**固定工作时间**？
- 道路救援类业务通常是24小时的，非工作时间来电如何处理？
  - [ ] 全部由机器人接待+记录，次日回拨
  - [ ] 安排夜班值班坐席
  - [ ] 紧急情况直接转值班手机

**11. 关于多业务线管理：**
- 道路救援、家政服务、代驾服务是**共用一个号码**，通过IVR分流？还是**不同业务独立号码**？
- 坐席是**混合技能组**（一个坐席可接多种业务），还是**专属技能组**（救援组、家政组、代驾组分开）？

---

## 第四轮：自动外呼深入

**12. 关于预测式外呼（提高人工效率）：**
- 外呼营销时，是否需要**预测式拨号**（系统自动批量拨打，接通后才转坐席）？
- 对于**接通率和坐席利用率**有什么期望指标吗？

**13. 关于无人化批量通知：**
- 典型的通知场景有哪些？
  - [ ] 服务人员即将到达通知
  - [ ] 订单确认/取消通知
  - [ ] 账单催缴提醒
  - [ ] 营销活动通知
- 通知需要**纯语音播报**，还是需要**交互确认**（如：按1确认，按2取消）？
- 通知失败后是否需要**自动重试**或**转短信**？

**14. 关于外呼数据来源：**
- 外呼名单从哪里来？
  - [ ] 从业务系统自动推送
  - [ ] 人工Excel导入
  - [ ] API实时触发（如：订单完成自动触发回访）

---

请您挑重点回答，我们离完整需求越来越近了！🚀

### USER
已经有了自己的CRM系统，需要呼叫中心系统集成CRM系统，客户来电叫服务时，识别客户身份，CRM系统弹屏，坐席在CRM系统上创建工单。呼叫中心是**7×24小时**运营，每个时间段都有坐席人员。不同业务独立号码，坐席**专属技能组**（救援组、家政组、代驾组分开）。外呼营销时，需要**预测式拨号，**对于**接通率和坐席利用率要能够统计。**典型的通知场景有：服务人员已经派出通知、服务人员即将到达通知，账单催缴提醒，通知需要**纯语音播报。**外呼名单从业务系统来。注意：坐席的操作方式是，是操作CRM系统或者业务系统，通过API的形式调用呼叫中心系统的呼入呼出功能。呼叫中心系统不需要有短信功能，由第三方的短信平台提供短信服务。

### ASSISTANT
非常关键的信息！您的架构思路非常清晰——**呼叫中心作为通信能力中台**，通过API为CRM/业务系统提供呼叫能力，这是目前主流且灵活的架构模式。

---

## 第五轮：技术架构与接口规范

**15. 关于CRM/业务系统集成方式：**
- 您的CRM系统是**Web端（B/S架构）**还是**桌面客户端（C/S架构）**？
- 坐席使用的话机形态是什么？
  - [ ] **软电话（网页/PC软件接听）**——WebRTC或SIP软电话
  - [ ] **IP话机（桌面硬件话机）**
  - [ ] **手机/手机App**
- 如果是软电话，您倾向于：
  - [ ] 呼叫中心提供**独立软电话条**（小组件嵌入CRM）
  - [ ] CRM系统**自行开发通话UI**，只调用呼叫中心API

**16. 关于API能力需求确认：**
请确认以下API能力是否需要：

| 功能分类 | 具体能力 | 是否需要 |
|---------|---------|---------|
| **呼入控制** | 来电弹屏通知（推送来电信息到CRM） | ? |
| | 接听/挂断/保持/取回 | ? |
| | 转接（转坐席/转技能组/转外线） | ? |
| | 三方通话/会议 | ? |
| **呼出控制** | 点击外呼（坐席点击号码拨打） | ? |
| | 预览式外呼（显示客户信息后手动拨打） | ? |
| | 预测式外呼（系统自动拨打，接通转坐席） | ? |
| **坐席状态** | 签入/签出/置忙/置闲/小休 | ? |
| | 实时坐席状态查询 | ? |
| **通话事件** | 通话状态回调（振铃、接通、挂断等） | ? |
| | 通话结束后话单推送 | ? |
| **录音** | 录音文件获取/下载 | ? |
| | 实时录音流（用于实时质检/转写） | ? |

**17. 关于来电分配策略（ACD）：**
- 同一技能组内多个坐席，来电如何分配？
  - [ ] 轮询（Round Robin）
  - [ ] 最长空闲优先
  - [ ] 技能值优先（技能等级高的优先）
  - [ ] 历史服务优先（上次是谁服务的优先给谁）
- 如果技能组全忙，是否需要**排队等待**功能？
  - [ ] 播放等待音乐/提示前面排队人数
  - [ ] 等待超时转其他组/留言/回拨

---

## 第六轮：机器人客服细化

**18. 关于呼入机器人（IVR+智能导航）：**
- IVR导航的复杂度如何？
  - [ ] **简单按键导航**：按1道路救援，按2家政服务，按3代驾...
  - [ ] **语音识别导航**：客户直接说"我要叫代驾"自动识别转接
  - [ ] **多轮对话**：机器人先收集信息（位置、服务类型）再转人工
- 常见问题解答需要支持多少个问题？（10个以内 / 10-50个 / 50个以上）
- 机器人**转人工的触发条件**是什么？
  - [ ] 客户说"转人工"
  - [ ] 客户按键选择
  - [ ] 机器人识别不了时自动转
  - [ ] 特定问题类型必须转人工（如投诉）

**19. 关于呼出机器人：**
- 回访/满意度调查的典型流程是什么？
  - 例如：播放"您好，请问您对本次服务满意吗？满意请按1，不满意请按2"
- 是否需要**采集客户按键结果**并回传业务系统？
- 如果客户不按键/无人接听，如何处理？

**20. 关于机器人技术选型：**
- 您是否有**指定的ASR/TTS供应商**？（如：阿里云、科大讯飞、百度等）
- 还是希望呼叫中心系统**内置语音能力**？
- 如果涉及智能对话（NLU），是否有指定的对话平台？还是需要系统提供？

---

## 第七轮：管理与运营功能

**21. 关于监控与管理：**
- 是否需要**实时监控大屏**？（显示：当前排队数、各组坐席状态、接通率等）
- 班长/主管需要哪些监控能力？
  - [ ] 实时监听坐席通话
  - [ ] 强插（加入通话协助）
  - [ ] 强拆（强制结束通话）
  - [ ] 密语（对坐席耳语指导，客户听不到）

**22. 关于统计报表：**
- 需要哪些维度的报表？
  - [ ] 坐席维度：接通量、通话时长、工作状态时长占比
  - [ ] 技能组维度：呼入量、接通率、排队放弃率
  - [ ] 时段维度：每小时/每日话务量趋势
  - [ ] 外呼任务维度：拨打量、接通率、意向客户数
- 报表是在**呼叫中心系统查看**，还是**数据推送到您的BI系统**？

**23. 关于录音与质检：**
- 所有通话都需要**全程录音**吗？
- 是否需要**质检功能**？
  - [ ] 人工质检（抽检打分）
  - [ ] 智能质检（语音转文字+关键词检测+情绪分析）

---

请您对以上问题进行确认和补充，尤其是**第16题API能力表格**，这对系统设计至关重要！📋

### USER
整套系统从零建设的话，大概需要多少预算？硬件和软件分别需要什么？

### ASSISTANT
非常好的问题！在进入详细预算前，我需要先确认几个影响成本的关键因素：

---

## 第八轮：预算与部署相关问题

**24. 关于部署方式：**
- 您倾向于哪种部署方式？
  - [ ] **私有化部署**（服务器放在自己机房或自有云主机）
  - [ ] **公有云部署**（阿里云、腾讯云、华为云等）
  - [ ] **混合部署**（核心私有化，部分能力用云服务）

**25. 关于线路资源：**
- 您目前是否已有**运营商线路资源**？
  - [ ] 已有中继线路（E1/SIP中继）
  - [ ] 已有95/96/400/1010号码
  - [ ] 没有，需要新申请
- 三种业务（道路救援、家政、代驾）计划用几个号码？

**26. 关于并发需求：**
- 150坐席同时工作，预估**最大并发通话数**是多少？
  - 通常按坐席数的 **70%-80%** 估算，即 **100-120路并发**
  - 您认为这个估算合理吗？

**27. 关于建设方式：**
- 您倾向于哪种方式？
  - [ ] **自研团队开发**（有FreeSWITCH开发能力）
  - [ ] **采购成熟产品**（购买商业呼叫中心系统）
  - [ ] **外包定制开发**（找供应商定制）

---

## 预算估算框架（先给您一个概览）

在您回答上述问题之前，我先给您一个**参考框架**，具体数字会根据您的回答调整：

### 一、硬件/基础设施预算

| 项目 | 私有化部署 | 云部署 |
|------|-----------|--------|
| **服务器** | 10-30万（3-5台服务器集群） | 云主机 3-8万/年 |
| **存储（录音）** | 5-15万（NAS/SAN存储） | 对象存储 1-3万/年 |
| **网络设备** | 2-5万（交换机、防火墙） | 含在云服务中 |
| **语音网关** | 3-10万（若用传统E1线路） | SIP中继无需网关 |
| **IP话机（可选）** | 300-800元/台 × 150 = 4.5-12万 | 同左 |
| **机房/带宽** | 5-10万/年 | 含在云服务中 |
| **小计** | **25-80万（一次性）+ 5-10万/年** | **5-15万/年** |

### 二、软件/系统预算

| 项目 | 采购商业产品 | 自研/定制开发 |
|------|------------|--------------|
| **呼叫中心平台** | 30-80万（按坐席授权） | 开发成本 50-150万 |
| **智能IVR/机器人** | 10-30万 + 调用费用 | 需对接ASR/TTS厂商 |
| **预测式外呼模块** | 10-20万 | 含在开发中 |
| **录音/质检系统** | 5-15万 | 含在开发中 |
| **API/集成开发** | 5-15万（定制费用） | 含在开发中 |
| **小计** | **60-160万** | **50-150万 + 团队成本** |

### 三、语音资源/运营费用

| 项目 | 预估费用 |
|------|---------|
| **号码月租** | 95/96号码：5000-2万/月；400号码：1000-5000/月 |
| **通话费** | 呼入：0.02-0.05元/分钟；呼出：0.08-0.15元/分钟 |
| **ASR/TTS调用** | 约 0.01-0.03元/秒（按使用量） |
| **线路并发费** | SIP中继：约 50-100元/路/月 |

**按日呼量5万、平均时长3分钟估算：**
- 月通话分钟数：5万 × 30天 × 3分钟 = **450万分钟/月**
- 呼入成本（40%）：180万分钟 × 0.03 = **5.4万/月**
- 呼出成本（60%）：270万分钟 × 0.10 = **27万/月**
- **月通信成本约：30-35万/月**

---

### 四、总体预算汇总（参考）

| 部署方式 | 首年投入 | 后续年度 |
|---------|---------|---------|
| **云部署 + 采购商业产品** | 70-180万 | 40-60万/年（含云资源+维保） |
| **私有化 + 采购商业产品** | 100-250万 | 20-40万/年（维保+资源） |
| **云部署 + 自研开发** | 80-200万 | 30-50万/年 + 研发团队 |

> ⚠️ 以上**不含通信费用**（每年约 **360-420万**，这是大头）

---

## 追问几个成本优化相关问题：

**28. 关于话机/通话终端：**
- 坐席是否可以接受**纯软电话**（用耳麦+电脑接听）？可节省硬件话机成本
- 还是必须配备**IP硬话机**？

**29. 关于录音存储：**
- 录音需要保存多长时间？（3个月 / 6个月 / 1年 / 更长）
- 日均5万通话 × 3分钟 ≈ 15万分钟/天，月存储需求约 **1-2TB**

**30. 关于智能语音：**
- 机器人对话的ASR/TTS，您倾向于：
  - [ ] 用云厂商API（按量付费，无需部署）
  - [ ] 私有化部署（一次性授权，成本较高但长期划算）

---

请您回答 **第24-30题**，我将结合您之前的回答，给出更精准的预算方案和硬件软件清单！💰

### USER
关于CRM/业务系统集成方式：CRM系统是 **Web端（B/S架构),**坐席使用的话机形态是：IP话机，但是要支持软电话功能，软电话使用呼叫中心提供**独立软电话条**（小组件嵌入CRM），列出的API能力都需要，来电分配策略：可以支持多种分配策略，首先历史服务优先，最长空闲优先和轮训。如果技能组全忙，可以设置溢出到其他技能组。IVR导航要支持语音识别导航，常见问题解答需要支持50个以上。机器人**转人工的触发条件**是：客户说"转人工"、机器人识别不了时自动转、特定问题类型必须转人工（如投诉）；机器人技术选型：指定的ASR/TTS供应商阿里云，如果涉及智能对话（NLU）由指定的对话平台。需要**实时监控大屏，**班长/主管需要监控能力：实时监听坐席通话、强插（加入通话协助）、强拆（强制结束通话）；所有通话都要全程录音；统计报表和质检本次先不涉及。

### ASSISTANT
非常详细！信息已经非常完整了。让我再确认最后几个关键问题，然后就可以为您生成完整的需求文档了。

---

## 第九轮：最终确认

**31. 关于部署方式（回顾第24题）：**
- 您倾向于 **私有化部署** 还是 **公有云部署**？

**32. 关于线路资源（回顾第25题）：**
- 是否已有运营商线路？还是需要新申请？
- 三种业务（道路救援、家政、代驾）分别用独立号码，计划用什么类型的号码？
  - [ ] 95/96短号（品牌形象好，申请门槛高）
  - [ ] 400号码（全国统一，成本适中）
  - [ ] 普通固话/手机号（成本低）

**33. 关于建设方式（回顾第27题）：**
- 您倾向于：
  - [ ] **采购成熟商业产品**
  - [ ] **自有团队基于FreeSWITCH自研**
  - [ ] **找供应商定制开发**

**34. 关于录音存储（回顾第29题）：**
- 录音需要保存多长时间？

**35. 关于外呼机器人补充：**
- 您之前提到呼出机器人用于「标准化通知、自动回访、满意度调查」
- 请确认回访/满意度调查是否需要**采集客户按键反馈**并回传业务系统？
  - 例如：「满意请按1，不满意请按2」-> 结果写回CRM

**36. 关于系统可用性要求：**
- 道路救援是紧急服务，对系统稳定性要求高
- 是否需要**主备高可用部署**？（即一台故障，自动切换到备机）

**37. 关于上线时间预期：**
- 您期望系统在多长时间内上线？
  - [ ] 1-2个月（紧急）
  - [ ] 3-4个月（正常）
  - [ ] 6个月以上（充裕）

---

请您回答以上问题后，我将为您生成一份完整的 **《呼叫中心系统需求规格说明书》**，包含：

1. **项目概述**（背景、目标、范围）
2. **业务需求**（呼入、呼出、机器人场景）
3. **功能需求**（分模块详细描述）
4. **接口需求**（API清单）
5. **非功能需求**（性能、安全、可用性）
6. **系统架构建议**
7. **硬件软件清单**
8. **预算估算**
9. **项目里程碑建议**

期待您的最后回复！📝

### USER
私有化部署，计划使用400号码和普通固话，建设方式：自有团队基于FreeSWITCH自研，录音需要长期保存，回访/满意度调查是需要**采集客户按键反馈**并回传业务系统；道路救援是紧急服务，对系统稳定性要求高且需要**主备高可用部署；**期望系统3-4个月上线。

### ASSISTANT
感谢您的耐心回答！所有关键信息已收集完毕。现在我为您生成完整的需求规格说明书。

---

# 企业呼叫中心系统需求规格说明书

**文档版本**：V1.0  
**编制日期**：2024年1月  
**文档状态**：初稿  

---

## 第一章 项目概述

### 1.1 项目背景

本企业主营道路救援、家政服务、代驾服务三大业务板块，面向个人消费者（ToC）提供服务。随着业务规模扩大，现需建设一套专业的呼叫中心系统，以提升客户服务体验、提高服务响应效率、降低人工成本。

### 1.2 项目目标

| 目标维度 | 具体目标 |
|---------|---------|
| **服务能力** | 支持150坐席同时在线，日处理呼叫量50,000通 |
| **响应效率** | 道路救援等紧急服务实现快速响应，7×24小时不间断服务 |
| **人工效率** | 通过预测式外呼提升坐席利用率，减少空闲等待时间 |
| **自动化水平** | 通过智能IVR和机器人实现常见问题自助解答、批量通知自动化 |
| **系统稳定性** | 主备高可用架构，确保核心业务（道路救援）不中断 |

### 1.3 项目范围

**纳入范围：**
- 基础呼叫能力（呼入/呼出）
- 智能IVR与语音导航
- 呼入/呼出机器人
- 预测式外呼
- 无人化批量通知
- CRM系统集成（API对接）
- 软电话条组件
- 实时监控大屏
- 班长监控功能
- 全程录音
- 主备高可用部署

**不纳入本期范围：**
- 统计报表系统（二期规划）
- 智能质检系统（二期规划）
- 短信功能（由第三方平台提供）

### 1.4 术语定义

| 术语 | 定义 |
|------|------|
| **ACD** | 自动呼叫分配（Automatic Call Distribution） |
| **IVR** | 交互式语音应答（Interactive Voice Response） |
| **ASR** | 自动语音识别（Automatic Speech Recognition） |
| **TTS** | 语音合成（Text To Speech） |
| **NLU** | 自然语言理解（Natural Language Understanding） |
| **CTI** | 计算机电话集成（Computer Telephony Integration） |
| **SIP** | 会话发起协议（Session Initiation Protocol） |

---

## 第二章 业务需求

### 2.1 业务架构

```
┌─────────────────────────────────────────────────────────────────┐
│                          客户触点                                │
│     道路救援热线          家政服务热线          代驾服务热线       │
│    (400-XXX-XXXX)       (400-XXX-XXXX)       (固话/400)         │
└───────────────┬─────────────────┬─────────────────┬─────────────┘
                │                 │                 │
                ▼                 ▼                 ▼
┌─────────────────────────────────────────────────────────────────┐
│                       呼叫中心系统                               │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐        │
│  │ 智能IVR  │  │ 呼入机器人│  │  ACD分配 │  │ 外呼系统 │        │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘        │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐        │
│  │ 坐席管理 │  │ 录音系统 │  │ 实时监控 │  │ API网关  │        │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘        │
└───────────────────────────┬─────────────────────────────────────┘
                            │ API
                            ▼
┌─────────────────────────────────────────────────────────────────┐
│                        CRM/业务系统                              │
│         客户管理    │    工单管理    │    派单系统               │
└─────────────────────────────────────────────────────────────────┘
```

### 2.2 呼入业务场景

#### 2.2.1 道路救援来电场景

**业务流程：**

```
客户来电 
    → 智能IVR（语音识别："道路救援"）
    → 机器人收集初步信息（可选）
    → 转救援技能组
    → ACD分配（历史服务优先 > 最长空闲 > 轮询）
    → 技能组全忙时溢出到备用组
    → 坐席接听 + CRM弹屏
    → 坐席在CRM创建工单/派单
    → 通话结束 → 录音保存
```

**业务要求：**
- 响应时效要求高，需优先保障接通率
- 支持7×24小时服务
- 来电需快速识别客户身份，展示历史服务记录

#### 2.2.2 家政服务来电场景

**业务流程：**

```
客户来电 
    → 智能IVR（按键或语音选择"家政服务"）
    → 常见问题解答（服务项目、价格咨询等）
    → 客户需要人工服务 → 转家政技能组
    → 坐席接听 + CRM弹屏
    → 预约服务/创建工单
    → 通话结束 → 录音保存
```

#### 2.2.3 代驾服务来电场景

**业务流程：**

```
客户来电 
    → 智能IVR（语音识别："叫代驾"）
    → 转代驾技能组
    → 坐席接听 + CRM弹屏
    → 创建代驾订单/派单
    → 通话结束 → 录音保存
```

#### 2.2.4 投诉来电场景

**业务流程：**

```
客户来电 
    → 智能IVR（识别"投诉"意图）
    → 直接转人工（投诉不经机器人处理）
    → 分配至对应业务技能组或投诉专席
    → CRM弹屏 + 标记投诉标签
    → 坐席记录投诉内容
```

### 2.3 呼出业务场景

#### 2.3.1 预测式外呼（营销/回访）

**业务流程：**

```
业务系统推送外呼任务（客户名单）
    → 呼叫中心批量拨打
    → 客户接听 → 自动转接空闲坐席
    → CRM弹屏展示客户信息
    → 坐席进行营销/回访
    → 通话结束 → 录音保存 → 话单回传业务系统
```

**业务要求：**
- 系统自动拨打，接通后才转坐席，减少坐席等待时间
- 支持接通率、坐席利用率统计
- 外呼任务支持暂停、恢复、停止

#### 2.3.2 机器人自动外呼（通知类）

**场景一：服务人员派出/即将到达通知**

```
业务系统触发通知（API调用）
    → 呼叫中心自动拨打客户电话
    → 播放TTS语音："您好，您预约的道路救援服务，师傅已出发，预计10分钟到达..."
    → 通话结束 → 结果回传业务系统
```

**场景二：账单催缴提醒**

```
业务系统推送催缴名单
    → 批量自动拨打
    → 播放TTS语音："您好，您有一笔待支付账单..."
    → 纯语音播报，无需交互
    → 拨打结果回传（接通/未接通/关机等）
```

#### 2.3.3 机器人自动外呼（回访/满意度调查）

**业务流程：**

```
业务系统推送回访任务
    → 呼叫中心自动拨打
    → 播放TTS语音："您好，请问您对本次服务满意吗？满意请按1，不满意请按2"
    → 采集客户按键
    → 按键结果回传业务系统
    → 不满意客户可选择转人工跟进
```

### 2.4 机器人客服场景

#### 2.4.1 呼入机器人功能定位

| 功能 | 说明 |
|------|------|
| **语音导航** | 支持客户语音说出需求，自动识别并转接对应技能组 |
| **常见问题解答** | 支持50个以上FAQ，自动语音解答 |
| **转人工预处理** | 收集客户初步信息后转人工，提高坐席效率 |

#### 2.4.2 转人工触发条件

| 触发条件 | 说明 |
|---------|------|
| 客户主动要求 | 客户说"转人工"或按键选择转人工 |
| 识别失败 | 机器人连续2-3次无法识别客户意图 |
| 特定问题类型 | 投诉类问题直接转人工，不经机器人处理 |

---

## 第三章 功能需求

### 3.1 智能IVR模块

#### 3.1.1 功能清单

| 功能编号 | 功能名称 | 功能描述 | 优先级 |
|---------|---------|---------|--------|
| IVR-001 | 多级语音菜单 | 支持配置多级IVR语音菜单，按键导航 | P0 |
| IVR-002 | 语音识别导航 | 集成阿里云ASR，支持客户语音说出需求自动识别 | P0 |
| IVR-003 | 多业务线路由 | 根据来电号码（400/固话）自动进入对应业务IVR流程 | P0 |
| IVR-004 | 时间条件路由 | 支持按工作时间/节假日配置不同IVR流程 | P1 |
| IVR-005 | 黑名单拦截 | 来电号码在黑名单中自动播放提示后挂断 | P1 |
| IVR-006 | VIP客户识别 | VIP客户来电自动优先排队或转专属坐席 | P2 |
| IVR-007 | IVR流程可视化配置 | 提供可视化拖拽界面配置IVR流程 | P1 |

#### 3.1.2 语音识别要求

- **ASR供应商**：阿里云语音识别
- **识别准确率**：≥90%
- **响应时间**：≤500ms
- **支持方言**：普通话为主，可选支持粤语、四川话等

### 3.2 ACD自动分配模块

#### 3.2.1 功能清单

| 功能编号 | 功能名称 | 功能描述 | 优先级 |
|---------|---------|---------|--------|
| ACD-001 | 技能组管理 | 支持创建多个技能组（救援组、家政组、代驾组） | P0 |
| ACD-002 | 坐席技能配置 | 坐席可属于一个或多个技能组，配置技能等级 | P0 |
| ACD-003 | 历史服务优先 | 来电客户优先分配给上次服务的坐席 | P0 |
| ACD-004 | 最长空闲优先 | 同技能组内优先分配给空闲时间最长的坐席 | P0 |
| ACD-005 | 轮询分配 | 同技能组内按顺序轮流分配 | P0 |
| ACD-006 | 分配策略优先级 | 支持配置分配策略优先级：历史服务 > 最长空闲 > 轮询 | P0 |
| ACD-007 | 技能组溢出 | 技能组全忙时，来电溢出到配置的备用技能组 | P0 |
| ACD-008 | 排队等待 | 所有坐席忙时，来电进入排队，播放等待音乐 | P0 |
| ACD-009 | 排队超时处理 | 排队超时可配置：继续等待/溢出/留言/回拨 | P1 |
| ACD-010 | 排队位置播报 | 向等待客户播报当前排队位置 | P2 |

### 3.3 坐席管理模块

#### 3.3.1 功能清单

| 功能编号 | 功能名称 | 功能描述 | 优先级 |
|---------|---------|---------|--------|
| AGT-001 | 坐席签入/签出 | 坐席上下班签入签出系统 | P0 |
| AGT-002 | 坐席状态管理 | 支持状态：空闲、通话中、振铃中、后处理、小休、忙碌 | P0 |
| AGT-003 | 小休原因 | 小休时需选择原因（用餐、洗手间、培训等） | P1 |
| AGT-004 | 状态自动切换 | 通话结束自动进入后处理状态，超时自动转空闲 | P0 |
| AGT-005 | 后处理时长配置 | 可配置后处理时长（如30秒） | P1 |
| AGT-006 | 强制签出 | 班长可强制签出坐席 | P1 |

#### 3.3.2 坐席工作台要求

- 坐席不使用呼叫中心独立工作台
- 坐席在CRM系统中操作业务
- 呼叫中心提供**软电话条组件**嵌入CRM页面
- 软电话条通过WebSocket与呼叫中心通信
- 同时支持IP话机（SIP协议注册）

### 3.4 软电话条组件

#### 3.4.1 功能清单

| 功能编号 | 功能名称 | 功能描述 | 优先级 |
|---------|---------|---------|--------|
| SFT-001 | 签入/签出 | 坐席登录CRM后通过软电话条签入呼叫中心 | P0 |
| SFT-002 | 状态切换 | 置忙/置闲/小休状态切换按钮 | P0 |
| SFT-003 | 来电弹屏触发 | 来电时向CRM发送弹屏事件，携带来电号码、客户ID等 | P0 |
| SFT-004 | 接听/挂断 | 点击接听/挂断按钮控制通话 | P0 |
| SFT-005 | 保持/取回 | 通话保持（播放等待音乐）和取回 | P0 |
| SFT-006 | 静音 | 坐席侧静音 | P1 |
| SFT-007 | 转接 | 转接至其他坐席/技能组/外线号码 | P0 |
| SFT-008 | 咨询转 | 先与第三方通话咨询，再决定是否转接 | P1 |
| SFT-009 | 三方通话 | 坐席、客户、第三方三方通话 | P1 |
| SFT-010 | 点击拨号 | 在CRM中点击号码呼出 | P0 |
| SFT-011 | 手动拨号 | 手动输入号码拨打 | P0 |
| SFT-012 | 通话计时 | 显示当前通话时长 | P0 |
| SFT-013 | 来电信息展示 | 显示来电号码、归属地、技能组等 | P0 |

#### 3.4.2 软电话条技术要求

| 要求项 | 说明 |
|-------|------|
| **前端技术** | Web组件，支持iframe嵌入或JS SDK集成 |
| **通信协议** | WebSocket长连接，实时接收呼叫事件 |
| **语音方案** | WebRTC（浏览器软电话）或SIP话机（硬件话机） |
| **浏览器支持** | Chrome 70+、Firefox 65+、Edge 79+ |

### 3.5 呼入通话控制

#### 3.5.1 功能清单

| 功能编号 | 功能名称 | 功能描述 | 优先级 |
|---------|---------|---------|--------|
| IBC-001 | 来电接入 | 支持SIP中继接入来电 | P0 |
| IBC-002 | 来电排队 | 坐席全忙时来电进入队列等待 | P0 |
| IBC-003 | 等待音乐 | 排队等待时播放等待音乐 | P0 |
| IBC-004 | 来电转接 | 支持盲转、咨询转、转技能组、转外线 | P0 |
| IBC-005 | 三方通话 | 支持三方会议通话 | P1 |
| IBC-006 | 通话保持 | 通话保持并播放等待音乐 | P0 |
| IBC-007 | 通话录音 | 全程录音，支持双声道（坐席/客户分轨）| P0 |

### 3.6 呼出通话控制

#### 3.6.1 功能清单

| 功能编号 | 功能名称 | 功能描述 | 优先级 |
|---------|---------|---------|--------|
| OBC-001 | 点击外呼 | 坐席点击号码发起外呼 | P0 |
| OBC-002 | 外显号码设置 | 支持配置外呼显示的主叫号码 | P0 |
| OBC-003 | 外呼转接 | 外呼通话中支持转接 | P0 |
| OBC-004 | 通话录音 | 外呼通话全程录音 | P0 |

### 3.7 预测式外呼模块

#### 3.7.1 功能清单

| 功能编号 | 功能名称 | 功能描述 | 优先级 |
|---------|---------|---------|--------|
| PDL-001 | 外呼任务创建 | 通过API创建外呼任务，导入号码名单 | P0 |
| PDL-002 | 任务状态控制 | 支持任务启动、暂停、恢复、停止 | P0 |
| PDL-003 | 预测式拨号算法 | 根据坐席空闲情况自动计算拨打频率 | P0 |
| PDL-004 | 接通转坐席 | 客户接听后自动转接空闲坐席 | P0 |
| PDL-005 | 无人接听处理 | 无人接听/忙音/关机等自动标记状态 | P0 |
| PDL-006 | 外呼时段限制 | 可配置允许外呼的时间段（如9:00-21:00） | P1 |
| PDL-007 | 号码去重 | 同一任务内号码自动去重 | P1 |
| PDL-008 | 重试策略 | 未接通号码可配置自动重试次数和间隔 | P1 |
| PDL-009 | 坐席利用率统计 | 统计坐席有效通话时长占比 | P0 |
| PDL-010 | 接通率统计 | 统计任务接通率 | P0 |

### 3.8 机器人外呼模块

#### 3.8.1 功能清单

| 功能编号 | 功能名称 | 功能描述 | 优先级 |
|---------|---------|---------|--------|
| RBT-001 | 批量通知任务 | 通过API创建批量语音通知任务 | P0 |
| RBT-002 | TTS语音播放 | 集成阿里云TTS，文本转语音播放 | P0 |
| RBT-003 | 变量替换 | 支持语音内容中插入变量（姓名、时间、金额等） | P0 |
| RBT-004 | 纯播报模式 | 播放语音后直接挂断 | P0 |
| RBT-005 | 按键采集模式 | 播放语音后等待客户按键，采集结果 | P0 |
| RBT-006 | 按键结果回传 | 按键结果通过回调接口推送至业务系统 | P0 |
| RBT-007 | 无应答处理 | 客户无应答/超时可配置重试或标记 | P1 |
| RBT-008 | 并发控制 | 可配置任务最大并发拨打数 | P1 |
| RBT-009 | 任务进度查询 | API查询任务进度和结果 | P0 |

### 3.9 呼入机器人模块

#### 3.9.1 功能清单

| 功能编号 | 功能名称 | 功能描述 | 优先级 |
|---------|---------|---------|--------|
| IRB-001 | 语音识别 | 集成阿里云ASR识别客户语音 | P0 |
| IRB-002 | 意图识别 | 对接NLU平台，识别客户意图 | P0 |
| IRB-003 | FAQ问答 | 支持配置50+常见问题及答案 | P0 |
| IRB-004 | 语音合成应答 | 集成阿里云TTS合成语音应答 | P0 |
| IRB-005 | 多轮对话 | 支持多轮对话收集信息 | P1 |
| IRB-006 | 转人工-主动请求 | 客户说"转人工"触发转接 | P0 |
| IRB-007 | 转人工-识别失败 | 连续N次识别失败自动转人工 | P0 |
| IRB-008 | 转人工-特定意图 | 识别到投诉等意图直接转人工 | P0 |
| IRB-009 | 转接携带信息 | 转人工时携带已收集的信息至坐席 | P1 |
| IRB-010 | 话术配置 | 支持配置机器人话术和回复内容 | P0 |

### 3.10 录音管理模块

#### 3.10.1 功能清单

| 功能编号 | 功能名称 | 功能描述 | 优先级 |
|---------|---------|---------|--------|
| REC-001 | 全程录音 | 所有呼入呼出通话全程录音 | P0 |
| REC-002 | 双声道录音 | 坐席和客户分轨录音（便于质检） | P1 |
| REC-003 | 录音存储 | 录音文件存储至NAS/分布式存储 | P0 |
| REC-004 | 长期保存 | 支持录音长期保存，定期归档 | P0 |
| REC-005 | 录音检索 | 按通话ID、时间、号码等检索录音 | P0 |
| REC-006 | 录音下载 | 通过API获取录音文件下载地址 | P0 |
| REC-007 | 录音权限 | 录音访问权限控制 | P1 |

### 3.11 班长监控模块

#### 3.11.1 功能清单

| 功能编号 | 功能名称 | 功能描述 | 优先级 |
|---------|---------|---------|--------|
| SUP-001 | 坐席状态监控 | 实时查看所有坐席当前状态 | P0 |
| SUP-002 | 实时监听 | 监听坐席与客户通话，双方不知情 | P0 |
| SUP-003 | 强插通话 | 加入通话，三方均可听到 | P0 |
| SUP-004 | 强拆通话 | 强制结束坐席通话 | P0 |
| SUP-005 | 密语（可选） | 对坐席单向说话，客户听不到（二期） | P2 |
| SUP-006 | 强制签出 | 强制将坐席签出系统 | P1 |
| SUP-007 | 强制置忙 | 强制将坐席状态设为忙碌 | P1 |

### 3.12 实时监控大屏

#### 3.12.1 功能清单

| 功能编号 | 功能名称 | 功能描述 | 优先级 |
|---------|---------|---------|--------|
| DSP-001 | 整体概览 | 展示当前呼入量、呼出量、接通率等 | P0 |
| DSP-002 | 坐席状态分布 | 展示各状态坐席数量（空闲、通话、小休等） | P0 |
| DSP-003 | 技能组监控 | 各技能组排队数、等待时长、可用坐席数 | P0 |
| DSP-004 | 实时告警 | 排队超阈值、坐席空闲过少等告警提示 | P1 |
| DSP-005 | 趋势图表 | 当日话务量趋势图（按小时） | P1 |
| DSP-006 | 大屏适配 | 支持大屏显示器展示 | P0 |

---

## 第四章 接口需求

### 4.1 接口概述

呼叫中心系统作为通信能力中台，通过API方式为CRM/业务系统提供呼叫能力。接口采用RESTful API + WebSocket事件推送模式。

### 4.2 接口清单

#### 4.2.1 呼入控制接口

| 接口编号 | 接口名称 | 请求方式 | 说明 |
|---------|---------|---------|------|
| API-IN-001 | 接听来电 | POST | 坐席接听当前来电 |
| API-IN-002 | 挂断通话 | POST | 挂断当前通话 |
| API-IN-003 | 保持通话 | POST | 将通话置于保持状态 |
| API-IN-004 | 取回通话 | POST | 取回保持的通话 |
| API-IN-005 | 转接通话 | POST | 转接至指定坐席/技能组/外线 |
| API-IN-006 | 咨询通话 | POST | 发起咨询通话 |
| API-IN-007 | 咨询转移 | POST | 咨询后转移 |
| API-IN-008 | 三方通话 | POST | 建立三方通话 |

#### 4.2.2 呼出控制接口

| 接口编号 | 接口名称 | 请求方式 | 说明 |
|---------|---------|---------|------|
| API-OUT-001 | 点击外呼 | POST | 坐席发起外呼 |
| API-OUT-002 | 取消外呼 | POST | 取消正在拨打的外呼 |
| API-OUT-003 | 预览式外呼确认 | POST | 坐席确认拨打预览号码 |

#### 4.2.3 坐席状态接口

| 接口编号 | 接口名称 | 请求方式 | 说明 |
|---------|---------|---------|------|
| API-AGT-001 | 坐席签入 | POST | 坐席签入系统 |
| API-AGT-002 | 坐席签出 | POST | 坐席签出系统 |
| API-AGT-003 | 置忙 | POST | 坐席设置为忙碌状态 |
| API-AGT-004 | 置闲 | POST | 坐席设置为空闲状态 |
| API-AGT-005 | 小休 | POST | 坐席进入小休状态 |
| API-AGT-006 | 查询坐席状态 | GET | 查询指定坐席当前状态 |
| API-AGT-007 | 批量查询坐席状态 | GET | 批量查询坐席状态 |

#### 4.2.4 外呼任务接口

| 接口编号 | 接口名称 | 请求方式 | 说明 |
|---------|---------|---------|------|
| API-TASK-001 | 创建外呼任务 | POST | 创建预测式/机器人外呼任务 |
| API-TASK-002 | 导入号码 | POST | 向任务导入外呼号码 |
| API-TASK-003 | 启动任务 | POST | 启动外呼任务 |
| API-TASK-004 | 暂停任务 | POST | 暂停外呼任务 |
| API-TASK-005 | 恢复任务 | POST | 恢复暂停的任务 |
| API-TASK-006 | 停止任务 | POST | 停止外呼任务 |
| API-TASK-007 | 查询任务进度 | GET | 查询任务执行进度 |
| API-TASK-008 | 查询任务结果 | GET | 查询任务拨打结果明细 |

#### 4.2.5 单条外呼接口（通知类）

| 接口编号 | 接口名称 | 请求方式 | 说明 |
|---------|---------|---------|------|
| API-NOTIFY-001 | 发起语音通知 | POST | 触发单条语音通知外呼 |
| API-NOTIFY-002 | 查询通知结果 | GET | 查询通知拨打结果 |

#### 4.2.6 录音接口

| 接口编号 | 接口名称 | 请求方式 | 说明 |
|---------|---------|---------|------|
| API-REC-001 | 查询录音列表 | GET | 按条件查询录音记录 |
| API-REC-002 | 获取录音下载地址 | GET | 获取录音文件下载URL |

#### 4.2.7 监控接口

| 接口编号 | 接口名称 | 请求方式 | 说明 |
|---------|---------|---------|------|
| API-MON-001 | 监听通话 | POST | 班长监听指定坐席通话 |
| API-MON-002 | 强插通话 | POST | 班长强插指定坐席通话 |
| API-MON-003 | 强拆通话 | POST | 班长强制挂断指定坐席通话 |
| API-MON-004 | 实时统计数据 | GET | 获取实时监控统计数据 |
| API-MON-005 | 技能组排队信息 | GET | 获取各技能组排队情况 |

### 4.3 事件推送（WebSocket）

#### 4.3.1 通话事件

| 事件编号 | 事件名称 | 触发时机 | 推送内容 |
|---------|---------|---------|---------|
| EVT-001 | 来电振铃 | 来电分配到坐席时 | 来电号码、客户ID、技能组、IVR收集信息等 |
| EVT-002 | 通话接通 | 坐席接听后 | 通话ID、接通时间 |
| EVT-003 | 通话挂断 | 通话结束后 | 通话ID、挂断方、通话时长 |
| EVT-004 | 转接振铃 | 转接到新坐席振铃时 | 转接来源、来电信息 |
| EVT-005 | 外呼振铃 | 外呼拨打中 | 外呼号码、坐席ID |
| EVT-006 | 外呼接通 | 客户接听 | 通话ID |
| EVT-007 | 外呼失败 | 外呼未接通 | 失败原因（无人接听/忙/关机等） |

#### 4.3.2 坐席状态事件

| 事件编号 | 事件名称 | 触发时机 | 推送内容 |
|---------|---------|---------|---------|
| EVT-101 | 状态变更 | 坐席状态变化时 | 坐席ID、新状态、旧状态、时间 |
| EVT-102 | 签入成功 | 坐席签入成功 | 坐席ID、技能组 |
| EVT-103 | 签出成功 | 坐席签出成功 | 坐席ID |

#### 4.3.3 话单推送

| 事件编号 | 事件名称 | 触发时机 | 推送内容 |
|---------|---------|---------|---------|
| EVT-201 | 话单推送 | 通话结束后 | 通话ID、主被叫、开始时间、结束时间、时长、录音地址、坐席ID、技能组、呼叫类型等 |

#### 4.3.4 机器人交互结果推送

| 事件编号 | 事件名称 | 触发时机 | 推送内容 |
|---------|---------|---------|---------|
| EVT-301 | 按键结果 | 客户按键后 | 任务ID、号码、按键值、时间 |
| EVT-302 | 机器人通话结果 | 机器人外呼结束 | 任务ID、号码、接通状态、通话时长、按键结果 |

---

## 第五章 非功能需求

### 5.1 性能需求

| 指标项 | 要求 |
|-------|------|
| **并发坐席数** | 支持150坐席同时在线 |
| **并发通话数** | 支持120路并发通话（含呼入+呼出） |
| **日处理量** | 支持日均50,000通呼叫 |
| **呼叫建立时延** | 来电从接入到振铃坐席 ≤ 1秒 |
| **API响应时间** | 95%请求响应 ≤ 200ms |
| **事件推送时延** | 事件产生到推送至CRM ≤ 500ms |
| **ASR识别延迟** | 语音识别延迟 ≤ 500ms |
| **系统可用性** | ≥ 99.9%（全年停机不超过8.76小时） |

### 5.2 高可用需求

| 要求项 | 说明 |
|-------|------|
| **主备部署** | FreeSWITCH主备双机部署，主机故障自动切换至备机 |
| **切换时间** | 故障切换时间 ≤ 30秒 |
| **数据同步** | 主备配置实时同步 |
| **心跳检测** | 主备心跳检测间隔 ≤ 5秒 |
| **数据库高可用** | 数据库采用主从复制或集群部署 |
| **录音存储** | 录音存储采用RAID或分布式存储，防止数据丢失 |

### 5.3 安全需求

| 要求项 | 说明 |
|-------|------|
| **网络安全** | SIP通信支持TLS加密；API支持HTTPS |
| **API认证** | API调用需Token认证，支持IP白名单 |
| **权限控制** | 支持角色权限管理（管理员、班长、坐席） |
| **录音安全** | 录音文件访问需权限验证 |
| **操作审计** | 关键操作记录审计日志 |

### 5.4 兼容性需求

| 要求项 | 说明 |
|-------|------|
| **SIP话机** | 兼容主流SIP话机品牌（如：Yealink、Grandstream、Fanvil等） |
| **SIP协议** | 符合RFC 3261标准 |
| **浏览器** | 软电话条支持Chrome 70+、Firefox 65+、Edge 79+ |
| **编解码** | 语音编解码支持G.711a、G.711u、G.729（可选） |

### 5.5 可扩展性需求

| 要求项 | 说明 |
|-------|------|
| **坐席扩展** | 架构支持水平扩展至300+坐席 |
| **业务线扩展** | 支持新增业务线/技能组，无需改动核心架构 |
| **线路扩展** | 支持新增SIP中继线路 |

---

## 第六章 系统架构设计建议

### 6.1 整体架构图

```
                           ┌──────────────────┐
                           │   运营商网络      │
                           │ (SIP Trunk/400)  │
                           └────────┬─────────┘
                                    │
                           ┌────────▼─────────┐
                           │   SBC/边界网关    │
                           │  (可选，安全防护)  │
                           └────────┬─────────┘
                                    │
        ┌───────────────────────────┼───────────────────────────┐
        │                           │                           │
        │    ┌──────────────────────▼─────────────────────┐    │
        │    │                 FreeSWITCH                  │    │
        │    │              (主备双机部署)                  │    │
        │    │  ┌─────────┬─────────┬─────────┬─────────┐ │    │
        │    │  │   IVR   │   ACD   │ 录音模块 │ 外呼引擎│ │    │
        │    │  └─────────┴─────────┴─────────┴─────────┘ │    │
        │    └───────────────────────┬────────────────────┘    │
        │                            │                          │
        │    ┌───────────────────────┼───────────────────────┐ │
        │    │                       │                       │ │
        │    ▼                       ▼                       ▼ │
        │ ┌──────────┐       ┌──────────────┐       ┌────────┐│
        │ │ CTI中间件 │       │   API Gateway │       │ WS推送 ││
        │ │(ESL连接)  │       │   (REST API)  │       │  服务  ││
        │ └────┬─────┘       └──────┬───────┘       └───┬────┘│
        │      │                    │                    │     │
        │      └────────────────────┼────────────────────┘     │
        │                           │                          │
        │          ┌────────────────┼────────────────┐        │
        │          │                │                │        │
        │          ▼                ▼                ▼        │
        │   ┌──────────┐    ┌──────────┐    ┌───────────┐    │
        │   │  MySQL   │    │  Redis   │    │ 文件存储   │    │
        │   │ (话单/配置)│    │ (缓存/状态)│    │  (录音)   │    │
        │   └──────────┘    └──────────┘    └───────────┘    │
        │                                                      │
        │                   呼叫中心系统边界                    │
        └───────────────────────────┬──────────────────────────┘
                                    │
            ┌───────────────────────┼───────────────────────┐
            │                       │                       │
            ▼                       ▼                       ▼
     ┌──────────┐           ┌──────────┐            ┌────────────┐
     │ CRM系统   │           │ 业务系统  │            │ 阿里云语音  │
     │(弹屏/工单) │           │(派单/订单)│            │(ASR/TTS)   │
     └──────────┘           └──────────┘            └────────────┘
```

### 6.2 核心组件说明

| 组件 | 技术选型 | 说明 |
|------|---------|------|
| **媒体服务器** | FreeSWITCH | 核心呼叫控制、媒体处理、IVR、录音 |
| **CTI中间件** | 自研（基于ESL） | 封装FreeSWITCH ESL接口，提供业务逻辑 |
| **API网关** | Nginx/Kong/自研 | RESTful API统一入口 |
| **WebSocket服务** | Node.js/Golang | 实时事件推送 |
| **数据库** | MySQL 8.0 | 话单、配置、坐席信息存储 |
| **缓存** | Redis | 坐席状态、会话信息缓存 |
| **录音存储** | NAS/MinIO/对象存储 | 录音文件长期存储 |
| **ASR/TTS** | 阿里云智能语音 | 语音识别与合成 |
| **NLU** | 外部指定平台 | 意图识别与对话管理 |

### 6.3 部署架构（主备高可用）

```
                    ┌─────────────────────────────────────┐
                    │            VIP（虚拟IP）             │
                    └──────────────────┬──────────────────┘
                                       │
            ┌──────────────────────────┼──────────────────────────┐
            │                          │                          │
            ▼                          │                          ▼
  ┌─────────────────┐                  │                ┌─────────────────┐
  │   FreeSWITCH    │◄─────Keepalived心跳──────────────►│   FreeSWITCH    │
  │     (主机)       │                  │                │     (备机)       │
  │   CTI中间件      │                  │                │   CTI中间件      │
  │   API服务       │                  │                │   API服务       │
  └─────────────────┘                  │                └─────────────────┘
            │                          │                          │
            └──────────────────────────┼──────────────────────────┘
                                       │
                          ┌────────────┴────────────┐
                          │                         │
                          ▼                         ▼
                  ┌──────────────┐         ┌───────────────┐
                  │ MySQL主从集群 │         │ Redis Sentinel│
                  └──────────────┘         └───────────────┘
                          │
                          ▼
                  ┌───────────────┐
                  │  NAS/分布式存储 │
                  │   (录音文件)    │
                  └───────────────┘
```

---

## 第七章 硬件与软件清单

### 7.1 硬件清单

| 序号 | 设备类型 | 规格配置 | 数量 | 用途 | 参考单价（万元） | 合计（万元） |
|-----|---------|---------|------|------|---------------|-------------|
| 1 | 应用服务器（主） | 32核CPU/64GB内存/500GB SSD+2TB HDD | 1 | FreeSWITCH+CTI+API | 5-8 | 5-8 |
| 2 | 应用服务器（备） | 32核CPU/64GB内存/500GB SSD+2TB HDD | 1 | FreeSWITCH备机 | 5-8 | 5-8 |
| 3 | 数据库服务器 | 16核CPU/64GB内存/1TB SSD | 2 | MySQL主从 | 4-6 | 8-12 |
| 4 | Redis服务器 | 8核CPU/32GB内存/500GB SSD | 3 | Redis Sentinel集群 | 2-3 | 6-9 |
| 5 | NAS存储 | 96TB可用空间（RAID） | 1 | 录音长期存储 | 8-15 | 8-15 |
| 6 | 网络交换机 | 千兆三层交换机 | 2 | 网络通信 | 1-2 | 2-4 |
| 7 | 防火墙 | 企业级防火墙 | 1 | 安全防护 | 2-5 | 2-5 |
| 8 | IP话机 | SIP话机（如Yealink T46U） | 150 | 坐席通话终端 | 0.05-0.08 | 7.5-12 |
| 9 | 坐席耳麦 | 专业话务耳麦 | 150 | 坐席配件 | 0.02-0.05 | 3-7.5 |
| **硬件合计** | | | | | | **47-80.5** |

### 7.2 软件清单

| 序号 | 软件/平台 | 说明 | 授权方式 | 预估费用（万元） |
|-----|---------|------|---------|----------------|
| 1 | FreeSWITCH | 开源软交换平台 | 开源免费 | 0 |
| 2 | Linux操作系统 | CentOS/Rocky Linux | 开源免费 | 0 |
| 3 | MySQL数据库 | 社区版 | 开源免费 | 0 |
| 4 | Redis | 缓存 | 开源免费 | 0 |
| 5 | Nginx | 反向代理/负载均衡 | 开源免费 | 0 |
| 6 | 阿里云ASR | 语音识别 | 按量付费 | 详见运营费用 |
| 7 | 阿里云TTS | 语音合成 | 按量付费 | 详见运营费用 |
| 8 | NLU对话平台 | 意图识别 | 按需对接 | 视平台定价 |
| 9 | 监控软件（Zabbix/Prometheus） | 系统监控 | 开源免费 | 0 |
| **软件合计** | | | | **0（开源为主）** |

### 7.3 开发成本估算

| 模块 | 预估人天 | 说明 |
|------|---------|------|
| FreeSWITCH环境搭建与配置 | 15 | 主备部署、线路对接、编解码配置 |
| IVR流程开发 | 20 | 多业务线IVR、语音导航 |
| ACD分配模块 | 25 | 多种分配策略、溢出逻辑 |
| CTI中间件开发 | 40 | ESL封装、业务逻辑 |
| 坐席状态管理 | 15 | 状态机、持久化 |
| API服务开发 | 30 | 全部API接口 |
| WebSocket推送服务 | 15 | 事件推送 |
| 软电话条组件 | 25 | 前端组件、WebRTC集成 |
| 预测式外呼引擎 | 30 | 外呼算法、任务管理 |
| 机器人外呼模块 | 20 | TTS集成、按键采集 |
| 呼入机器人模块 | 25 | ASR/NLU集成、对话流程 |
| 录音管理模块 | 10 | 录音存储、检索 |
| 班长监控模块 | 15 | 监听、强插、强拆 |
| 实时监控大屏 | 20 | 前端大屏、实时数据 |
| 高可用部署 | 15 | 主备切换、Keepalived |
| 联调测试 | 30 | 全流程测试 |
| **合计** | **350人天** | |

**按人天单价1500-2500元估算，开发成本约：52.5-87.5万元**

---

## 第八章 预算汇总

### 8.1 一次性投入

| 项目 | 费用（万元） |
|------|-------------|
| 硬件采购 | 47-80.5 |
| 软件（开源为主） | 0 |
| 开发成本 | 52.5-87.5 |
| 实施部署 | 5-10 |
| **一次性投入合计** | **104.5-178** |

### 8.2 年度运营费用

| 项目 | 月费用（万元） | 年费用（万元） | 备注 |
|------|--------------|---------------|------|
| **线路月租** | | | |
| 400号码（3个） | 0.3-1.5 | 3.6-18 | 3条业务线独立号码 |
| SIP中继并发费（120路） | 0.6-1.2 | 7.2-14.4 | 按50-100元/路/月 |
| **通信费** | | | |
| 呼入通话费 | 5-9 | 60-108 | 180万分钟/月×0.03-0.05元 |
| 呼出通话费 | 21.6-40.5 | 259-486 | 270万分钟/月×0.08-0.15元 |
| **云服务费** | | | |
| 阿里云ASR | 2-5 | 24-60 | 按使用量 |
| 阿里云TTS | 1-3 | 12-36 | 按使用量 |
| **其他** | | | |
| 机房/带宽 | 1-2 | 12-24 | 如自有机房可降低 |
| 系统维护 | 1-2 | 12-24 | 人员/备件 |
| **年度运营合计** | | **390-770** | |

### 8.3 总预算汇总

| 项目 | 费用（万元） |
|------|-------------|
| **首年投入** | 494.5-948（一次性+首年运营） |
| **后续每年** | 390-770（运营费用） |

> ⚠️ **说明：** 
> - 通信费用是大头，约占年度成本的80%以上
> - 具体费用取决于运营商议价、实际话务量
> - 可通过接通率优化、IVR分流等降低通信成本

---

## 第九章 项目里程碑建议

按照3-4个月上线周期规划：

| 阶段 | 周期 | 主要工作 | 交付物 |
|------|------|---------|--------|
| **一、需求确认与设计** | 第1-2周 | 需求评审、技术方案设计、架构设计 | 技术方案文档、接口设计文档 |
| **二、环境搭建** | 第3-4周 | 硬件采购到货、FreeSWITCH环境部署、线路对接 | 基础环境就绪 |
| **三、核心功能开发** | 第5-10周 | CTI中间件、API服务、坐席管理、呼入呼出基础功能 | 核心功能可用 |
| **四、高级功能开发** | 第11-14周 | 预测式外呼、机器人模块、IVR、监控大屏 | 全功能开发完成 |
| **五、集成联调** | 第15-16周 | CRM对接、全流程联调、高可用测试 | 联调通过 |
| **六、测试与优化** | 第17-18周 | 功能测试、性能测试、Bug修复 | 测试报告 |
| **七、试运行** | 第19-20周 | 小范围上线、问题收集、优化调整 | 试运行报告 |
| **八、正式上线** | 第21周 | 全面上线、监控保障 | 系统上线 |

```
第1月        第2月        第3月        第4月        第5月
|----|----|----|----|----|----|----|----|----|----|
[需求设计][环境搭建][====核心功能开发====][==高级功能==]
                                        [集成联调][测试]
                                                 [试运行][上线]
```

---

## 第十章 风险与建议

### 10.1 项目风险

| 风险项 | 风险等级 | 影响 | 应对措施 |
|-------|---------|------|---------|
| FreeSWITCH技术门槛高 | 高 | 开发进度延迟 | 建议团队提前学习ESL开发，或引入有经验顾问 |
| 线路对接周期不可控 | 中 | 影响联调测试 | 提前与运营商沟通，预留缓冲时间 |
| ASR识别效果不及预期 | 中 | 影响机器人体验 | 提前做语料测试，必要时做热词优化 |
| 高可用切换影响业务 | 高 | 道路救援中断 | 充分测试切换机制，演练故障场景 |
| 录音存储空间不足 | 低 | 录音丢失 | 监控存储空间，提前扩容 |

### 10.2 建议事项

1. **团队能力准备**：建议团队提前熟悉FreeSWITCH、ESL、SIP协议相关知识
2. **分阶段上线**：可先上线基础呼入呼出功能，机器人功能迭代上线
3. **压力测试**：上线前务必进行150坐席并发的压力测试
4. **灰度发布**：建议先在一条业务线（如家政）试运行，再推广到道路救援
5. **监控告警**：完善监控体系，特别是对道路救援线路的可用性监控

---

## 附录

### 附录A：术语表
（见第一章1.4节）

### 附录B：接口详细设计
（需在技术设计阶段细化）

### 附录C：IVR流程图
（需在需求确认阶段补充）

### 附录D：机器人话术模板
（需在需求确认阶段补充）

---

**文档结束**

---

以上就是完整的需求规格说明书。请您审阅，如有需要补充或调整的内容，请随时告知！🎯

### USER
根据以上的文档，生成个项目立项书

### ASSISTANT
# 企业呼叫中心系统项目立项书

---

**项目名称**：企业呼叫中心系统建设项目  
**项目编号**：CC-2024-001  
**编制日期**：2024年1月  
**编 制 人**：_________________  
**审 核 人**：_________________  
**批 准 人**：_________________  

---

## 文档修订记录

| 版本 | 日期 | 修订内容 | 修订人 | 审核人 |
|------|------|---------|--------|--------|
| V1.0 | 2024-01-XX | 初稿 | | |

---

## 目录

1. [项目概述](#第一章-项目概述)
2. [项目背景与必要性](#第二章-项目背景与必要性)
3. [项目目标](#第三章-项目目标)
4. [项目范围](#第四章-项目范围)
5. [项目建设方案](#第五章-项目建设方案)
6. [项目投资预算](#第六章-项目投资预算)
7. [项目效益分析](#第七章-项目效益分析)
8. [项目实施计划](#第八章-项目实施计划)
9. [项目组织架构](#第九章-项目组织架构)
10. [风险分析与应对](#第十章-风险分析与应对)
11. [结论与建议](#第十一章-结论与建议)

---

## 第一章 项目概述

### 1.1 项目名称

企业呼叫中心系统建设项目

### 1.2 项目建设单位

**单位名称**：_________________（企业名称）  
**负 责 人**：_________________  
**联系方式**：_________________  

### 1.3 项目概要

本项目旨在建设一套集**呼入呼出、智能IVR、机器人客服、预测式外呼**于一体的企业级呼叫中心系统，支撑公司道路救援、家政服务、代驾服务三大业务板块的客户服务与营销运营工作。

| 项目要素 | 内容描述 |
|---------|---------|
| **服务对象** | 个人消费者（ToC） |
| **业务范围** | 道路救援、家政服务、代驾服务 |
| **坐席规模** | 150坐席 |
| **日话务量** | 50,000通/日 |
| **服务时间** | 7×24小时 |
| **建设周期** | 3-4个月 |
| **建设方式** | 自主研发（基于FreeSWITCH开源平台） |
| **部署方式** | 私有化部署（主备高可用架构） |

### 1.4 项目总投资

| 投资类别 | 金额（万元） |
|---------|-------------|
| 一次性建设投资 | 104.5-178 |
| 首年运营费用 | 390-770 |
| **首年总投入** | **494.5-948** |

---

## 第二章 项目背景与必要性

### 2.1 企业现状

本企业主营**道路救援、家政服务、代驾服务**三大业务，面向个人消费者提供上门服务。随着业务规模的持续扩大，现有的客户服务模式已无法满足业务发展需求：

**现状问题：**

| 问题领域 | 具体问题 | 影响 |
|---------|---------|------|
| **客服能力** | 无统一呼叫平台，多部门分散接听 | 客户体验差，管理困难 |
| **响应效率** | 道路救援等紧急需求响应慢 | 客户流失，品牌受损 |
| **服务时间** | 夜间/节假日服务能力不足 | 错失订单，客户投诉 |
| **外呼效率** | 人工逐一拨打，效率低下 | 人力成本高，触达率低 |
| **数据管理** | 通话无录音，无法追溯 | 纠纷处理困难，质量无法管控 |
| **系统集成** | 与CRM系统无法打通 | 信息孤岛，坐席重复操作 |

### 2.2 建设必要性

#### 2.2.1 业务发展的迫切需要

```
                    业务增长趋势
    话务量
      ↑
 60000│                              ╭──── 预测增长
      │                         ╭────╯
 50000│                    ╭────╯  ←── 当前日均50000通
      │               ╭────╯
 40000│          ╭────╯
      │     ╭────╯
 30000│╭────╯
      │
      └────────────────────────────────────→ 时间
        2022    2023    2024    2025
```

随着业务规模扩大，日话务量已达**50,000通**，预计未来2年将持续增长。迫切需要建设专业呼叫中心系统支撑业务发展。

#### 2.2.2 客户体验提升的需要

| 客户痛点 | 系统解决方案 |
|---------|-------------|
| 打电话难以接通 | 150坐席+智能排队+溢出机制 |
| 等待时间长 | IVR自助分流+机器人预处理 |
| 反复描述问题 | CRM弹屏，坐席即时了解客户信息 |
| 投诉处理无反馈 | 录音追溯+工单闭环 |

#### 2.2.3 运营效率提升的需要

| 运营痛点 | 系统解决方案 |
|---------|-------------|
| 外呼效率低 | 预测式外呼，坐席利用率提升50%+ |
| 通知类工作繁重 | 机器人自动批量通知 |
| 回访覆盖率低 | 机器人自动回访+满意度调查 |
| 管理缺乏抓手 | 实时监控大屏+班长监控 |

#### 2.2.4 合规与风控的需要

- 道路救援涉及紧急服务，需确保系统**高可用**
- 服务过程需**全程录音**，便于纠纷处理
- 需与现有CRM系统**无缝集成**，确保数据安全

### 2.3 建设可行性

#### 2.3.1 技术可行性

| 技术领域 | 可行性分析 |
|---------|-----------|
| **呼叫平台** | FreeSWITCH开源成熟，已广泛应用于企业呼叫中心 |
| **智能语音** | 阿里云ASR/TTS技术成熟，识别准确率>95% |
| **系统集成** | 标准API/WebSocket接口，与现有CRM可顺利对接 |
| **高可用** | 主备架构+Keepalived方案成熟可靠 |

#### 2.3.2 团队可行性

| 条件 | 评估 |
|------|------|
| 自有技术团队 | ✅ 具备自主开发能力 |
| FreeSWITCH经验 | ⚠️ 需提前培训或引入顾问支持 |
| 项目管理能力 | ✅ 具备中大型项目实施经验 |

#### 2.3.3 经济可行性

- 自主研发相比采购商业产品**节省授权费用**
- 私有化部署**长期成本可控**
- 项目投资回报分析详见第七章

---

## 第三章 项目目标

### 3.1 总体目标

建设一套**安全、稳定、高效、智能**的企业级呼叫中心系统，实现客户服务统一接入、智能化分流、高效外呼，全面提升客户服务体验与运营效率。

### 3.2 具体目标

#### 3.2.1 能力目标

| 目标项 | 指标要求 |
|-------|---------|
| 坐席并发能力 | 支持150坐席同时在线 |
| 通话并发能力 | 支持120路并发通话 |
| 日处理能力 | 支持50,000通/日 |
| 系统可用性 | ≥99.9%（全年停机<8.76小时） |
| 故障切换时间 | ≤30秒 |

#### 3.2.2 效率目标

| 目标项 | 当前值 | 目标值 | 提升幅度 |
|-------|-------|-------|---------|
| 人工坐席利用率 | 约40% | ≥70% | +75% |
| 外呼接通后转接时间 | N/A（人工拨号） | ≤3秒 | - |
| 批量通知人工耗时 | 100%人工 | 0%（全自动） | 释放人力 |
| 客户等待时长 | 无统计 | 平均<30秒 | - |

#### 3.2.3 体验目标

| 目标项 | 指标要求 |
|-------|---------|
| 首次呼叫解决率 | ≥80% |
| IVR自助分流率 | ≥30% |
| 客户满意度 | ≥90% |

### 3.3 建设原则

| 原则 | 说明 |
|------|------|
| **统一规划** | 三大业务线统一平台，避免重复建设 |
| **安全可靠** | 主备高可用，确保道路救援等紧急服务不中断 |
| **开放集成** | 标准API接口，与CRM/业务系统无缝对接 |
| **智能高效** | 引入AI能力，提升服务效率与体验 |
| **弹性扩展** | 架构支持未来扩展至300+坐席 |
| **自主可控** | 自主研发，核心技术自主掌握 |

---

## 第四章 项目范围

### 4.1 业务范围

| 业务线 | 呼入场景 | 呼出场景 |
|-------|---------|---------|
| **道路救援** | 叫救援、咨询、投诉 | 回访、满意度调查、到达通知 |
| **家政服务** | 预约服务、咨询、投诉 | 回访、营销推广、上门提醒 |
| **代驾服务** | 叫代驾、咨询、投诉 | 回访、营销推广、到达通知 |

### 4.2 功能范围

#### 4.2.1 本期建设范围（一期）

| 模块 | 功能项 | 优先级 |
|------|-------|--------|
| **基础通话** | 呼入接听、呼出拨打、转接、保持、三方通话 | P0 |
| **智能IVR** | 语音导航、按键导航、语音识别、多业务分流 | P0 |
| **ACD分配** | 技能组管理、多种分配策略、排队溢出 | P0 |
| **坐席管理** | 签入签出、状态管理、软电话条 | P0 |
| **预测式外呼** | 批量外呼任务、自动拨打、接通转坐席 | P0 |
| **机器人外呼** | 批量通知、TTS播报、按键采集 | P0 |
| **呼入机器人** | FAQ问答、意图识别、转人工 | P0 |
| **录音管理** | 全程录音、录音存储、录音检索下载 | P0 |
| **班长监控** | 监听、强插、强拆 | P0 |
| **监控大屏** | 实时数据展示、坐席状态、排队监控 | P0 |
| **系统集成** | CRM弹屏、API接口、事件推送 | P0 |
| **高可用** | 主备部署、故障切换 | P0 |

#### 4.2.2 二期规划范围（本期不含）

| 模块 | 功能项 | 说明 |
|------|-------|------|
| **统计报表** | 话务报表、坐席绩效、外呼分析 | 二期建设 |
| **智能质检** | 语音转写、关键词检测、情绪分析 | 二期建设 |
| **密语功能** | 班长对坐席单向指导 | 二期建设 |
| **短信功能** | 不涉及，由第三方平台提供 | 不纳入 |

### 4.3 集成范围

| 集成系统 | 集成方式 | 集成内容 |
|---------|---------|---------|
| **CRM系统** | API双向对接 | 来电弹屏、客户资料、工单创建 |
| **业务系统** | API对接 | 外呼任务推送、通知触发、结果回传 |
| **阿里云语音** | API调用 | ASR语音识别、TTS语音合成 |
| **NLU平台** | API对接 | 意图识别、对话管理 |

---

## 第五章 项目建设方案

### 5.1 总体架构

```
┌─────────────────────────────────────────────────────────────────────────┐
│                              用户层                                      │
│     道路救援客户          家政服务客户          代驾服务客户              │
│         │                    │                    │                     │
│         ▼                    ▼                    ▼                     │
│    400-XXX-XXX1         400-XXX-XXX2         固话/400                   │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                            接入层                                        │
│                      运营商SIP中继线路                                   │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                          呼叫控制层                                      │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │                    FreeSWITCH (主备高可用)                        │  │
│  │     ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐      │  │
│  │     │ 呼入控制│ │ 呼出控制│ │智能IVR │ │ ACD分配│ │ 录音模块│      │  │
│  │     └────────┘ └────────┘ └────────┘ └────────┘ └────────┘      │  │
│  └──────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                          业务服务层                                      │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐     │
│  │CTI中间件  │ │API Gateway│ │ WS推送   │ │ 外呼引擎 │ │ 机器人引擎│     │
│  └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘     │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                           数据层                                        │
│       ┌──────────┐      ┌──────────┐      ┌──────────────┐            │
│       │  MySQL   │      │  Redis   │      │   NAS存储     │            │
│       │ (主从集群) │      │(Sentinel)│      │   (录音)     │            │
│       └──────────┘      └──────────┘      └──────────────┘            │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                          集成层                                         │
│       ┌──────────┐      ┌──────────┐      ┌──────────────┐            │
│       │ CRM系统   │      │ 业务系统  │      │ 阿里云ASR/TTS│            │
│       └──────────┘      └──────────┘      └──────────────┘            │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                          终端层                                         │
│      ┌──────────────┐          ┌──────────────┐                        │
│      │   IP话机      │          │  软电话条     │                        │
│      │  (150台)      │          │ (嵌入CRM)    │                        │
│      └──────────────┘          └──────────────┘                        │
└─────────────────────────────────────────────────────────────────────────┘
```

### 5.2 部署架构

采用**私有化部署+主备高可用**架构：

```
                           ┌─────────────────┐
                           │   虚拟IP(VIP)   │
                           │  SIP线路接入    │
                           └────────┬────────┘
                                    │
              ┌─────────────────────┴─────────────────────┐
              │                                           │
              ▼                                           ▼
    ┌─────────────────┐                         ┌─────────────────┐
    │    主服务器      │◄──── Keepalived ────►│    备服务器      │
    ├─────────────────┤       心跳检测          ├─────────────────┤
    │  FreeSWITCH     │                         │  FreeSWITCH     │
    │  CTI中间件      │                         │  CTI中间件      │
    │  API服务        │                         │  API服务        │
    │  WebSocket服务  │                         │  WebSocket服务  │
    └────────┬────────┘                         └────────┬────────┘
             │                                           │
             └─────────────────────┬─────────────────────┘
                                   │
         ┌─────────────────────────┼─────────────────────────┐
         │                         │                         │
         ▼                         ▼                         ▼
┌─────────────────┐      ┌─────────────────┐      ┌─────────────────┐
│  MySQL主从集群   │      │ Redis Sentinel  │      │    NAS存储      │
│    (2节点)      │      │    (3节点)      │      │   (RAID冗余)    │
└─────────────────┘      └─────────────────┘      └─────────────────┘
```

### 5.3 技术选型

| 组件类型 | 技术选型 | 选型理由 |
|---------|---------|---------|
| **媒体服务器** | FreeSWITCH 1.10+ | 开源成熟、性能优异、功能完善 |
| **操作系统** | Rocky Linux 8/CentOS 8 | 稳定可靠、长期支持 |
| **开发语言** | Java/Golang | CTI中间件、API服务 |
| **前端框架** | Vue.js 3.x | 软电话条、监控大屏 |
| **数据库** | MySQL 8.0 | 成熟稳定、主从复制 |
| **缓存** | Redis 6.x | 高性能、Sentinel高可用 |
| **消息队列** | RabbitMQ/Kafka | 事件异步处理（可选） |
| **负载均衡** | Nginx | 反向代理、SSL卸载 |
| **高可用** | Keepalived | VIP漂移、故障切换 |
| **监控** | Prometheus + Grafana | 系统监控、告警 |
| **ASR/TTS** | 阿里云智能语音 | 识别准确率高、服务稳定 |

### 5.4 网络规划

| 网络区域 | 网段规划 | 用途 |
|---------|---------|------|
| **业务网段** | 10.10.1.0/24 | 呼叫中心服务器 |
| **数据网段** | 10.10.2.0/24 | 数据库、存储 |
| **坐席网段** | 10.10.10.0/23 | 坐席终端、IP话机 |
| **管理网段** | 10.10.100.0/24 | 运维管理 |
| **SIP信令** | 独立VLAN | SIP中继对接 |

### 5.5 号码规划

| 业务线 | 号码类型 | 号码 | 用途 |
|-------|---------|------|------|
| 道路救援 | 400号码 | 400-XXX-XXX1 | 客户呼入 |
| 家政服务 | 400号码 | 400-XXX-XXX2 | 客户呼入 |
| 代驾服务 | 400/固话 | 400-XXX-XXX3 | 客户呼入 |
| 外呼显号 | 固话 | 0XX-XXXXXXXX | 外呼主叫显示 |

---

## 第六章 项目投资预算

### 6.1 投资概算汇总

| 投资类别 | 金额（万元） | 占比 |
|---------|-------------|------|
| 硬件设备 | 47-80.5 | 33%-45% |
| 软件及开发 | 52.5-87.5 | 37%-49% |
| 实施部署 | 5-10 | 4%-6% |
| **一次性投资合计** | **104.5-178** | **100%** |

### 6.2 硬件投资明细

| 序号 | 设备名称 | 规格 | 数量 | 单价(万) | 合计(万) |
|-----|---------|------|------|---------|---------|
| 1 | 应用服务器 | 32核/64GB/500GB SSD+2TB | 2 | 5-8 | 10-16 |
| 2 | 数据库服务器 | 16核/64GB/1TB SSD | 2 | 4-6 | 8-12 |
| 3 | Redis服务器 | 8核/32GB/500GB SSD | 3 | 2-3 | 6-9 |
| 4 | NAS存储 | 96TB可用(RAID) | 1 | 8-15 | 8-15 |
| 5 | 网络交换机 | 千兆三层 | 2 | 1-2 | 2-4 |
| 6 | 防火墙 | 企业级 | 1 | 2-5 | 2-5 |
| 7 | IP话机 | SIP话机 | 150 | 0.05-0.08 | 7.5-12 |
| 8 | 坐席耳麦 | 专业话务耳麦 | 150 | 0.02-0.05 | 3-7.5 |
| | **硬件合计** | | | | **47-80.5** |

### 6.3 软件及开发投资明细

| 序号 | 项目 | 说明 | 费用(万) |
|-----|------|------|---------|
| 1 | FreeSWITCH | 开源免费 | 0 |
| 2 | 操作系统 | Rocky Linux开源 | 0 |
| 3 | MySQL/Redis | 社区版开源 | 0 |
| 4 | 系统开发 | 350人天×1500-2500元 | 52.5-87.5 |
| | **软件及开发合计** | | **52.5-87.5** |

### 6.4 实施部署投资

| 序号 | 项目 | 费用(万) |
|-----|------|---------|
| 1 | 硬件安装调试 | 1-2 |
| 2 | 网络部署配置 | 1-2 |
| 3 | 系统联调测试 | 2-4 |
| 4 | 培训及上线支持 | 1-2 |
| | **实施部署合计** | **5-10** |

### 6.5 年度运营费用

| 项目 | 月费用(万) | 年费用(万) |
|------|-----------|-----------|
| **线路费用** | | |
| 400号码月租(3个) | 0.3-1.5 | 3.6-18 |
| SIP并发费(120路) | 0.6-1.2 | 7.2-14.4 |
| **通信费用** | | |
| 呼入通话费 | 5-9 | 60-108 |
| 呼出通话费 | 21.6-40.5 | 259-486 |
| **云服务费用** | | |
| 阿里云ASR | 2-5 | 24-60 |
| 阿里云TTS | 1-3 | 12-36 |
| **运维费用** | | |
| 机房/带宽 | 1-2 | 12-24 |
| 系统维护 | 1-2 | 12-24 |
| **年度运营合计** | | **390-770** |

### 6.6 投资估算汇总表

| 项目 | 首年(万) | 第二年(万) | 第三年(万) |
|------|---------|-----------|-----------|
| 一次性建设投入 | 104.5-178 | - | - |
| 年度运营费用 | 390-770 | 390-770 | 390-770 |
| **年度总计** | **494.5-948** | **390-770** | **390-770** |
| **三年累计** | | | **1274.5-2488** |

---

## 第七章 项目效益分析

### 7.1 经济效益

#### 7.1.1 人力成本节约

| 节约项 | 计算依据 | 年节约(万) |
|-------|---------|-----------|
| **外呼效率提升** | 坐席利用率从40%→70%，相当于节约45个坐席人力 | 45×8=360 |
| **批量通知自动化** | 原需5人专职，现全自动 | 5×8=40 |
| **回访自动化** | 原需10人，现机器人承担80% | 8×8=64 |
| **IVR自助分流** | 30%简单咨询自助解决，节约人力 | 15×8=120 |
| **年人力成本节约** | | **约584万** |

> 注：按坐席人均年成本8万元估算

#### 7.1.2 业务增收

| 增收项 | 计算依据 | 年增收(万) |
|-------|---------|-----------|
| **接通率提升** | 减少客户流失，预计增收5% | 约200 |
| **外呼营销转化** | 新增营销触达能力 | 约150 |
| **年业务增收** | | **约350万** |

#### 7.1.3 投资回报分析

| 指标 | 数值 |
|------|------|
| 一次性投入 | 141万（取中值） |
| 年运营成本 | 580万（取中值） |
| 年成本节约 | 584万 |
| 年业务增收 | 350万 |
| **年净收益** | **354万** |
| **投资回收期** | **约4.8个月** |

### 7.2 管理效益

| 效益项 | 说明 |
|-------|------|
| **服务可追溯** | 全程录音，服务质量可追溯，纠纷可举证 |
| **管理可视化** | 实时监控大屏，管理者实时掌握运营状态 |
| **决策有依据** | 话务数据积累，为排班、扩容提供数据支撑 |
| **流程标准化** | IVR+机器人固化服务流程，服务质量稳定 |

### 7.3 客户效益

| 效益项 | 说明 |
|-------|------|
| **接通率提升** | 专业排队机制，减少占线挂断 |
| **等待时间缩短** | IVR分流+智能分配，平均等待<30秒 |
| **服务体验提升** | 来电自动识别，坐席了解客户历史，避免重复描述 |
| **服务时间扩展** | 7×24小时服务，紧急需求随时响应 |

---

## 第八章 项目实施计划

### 8.1 项目周期

**总工期：4个月（17周）**

### 8.2 阶段划分

```
    1月                2月                3月                4月            5月
    |----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|
W1  W2  W3  W4  W5  W6  W7  W8  W9  W10 W11 W12 W13 W14 W15 W16 W17 W18

[需求设计 ]
    [环境搭建  ]
            [========= 核心功能开发 =========]
                                    [=== 高级功能开发 ===]
                                                    [集成联调]
                                                            [测试优化]
                                                                    [试运行][上线]
```

### 8.3 里程碑计划

| 里程碑 | 计划时间 | 交付物 | 验收标准 |
|-------|---------|-------|---------|
| **M1: 需求确认** | 第2周末 | 需求规格说明书、技术方案 | 评审通过 |
| **M2: 环境就绪** | 第4周末 | 硬件到位、基础环境部署完成 | FreeSWITCH可接打电话 |
| **M3: 核心功能完成** | 第10周末 | 呼入呼出、ACD、坐席管理、API | 核心流程跑通 |
| **M4: 全功能完成** | 第14周末 | 外呼、机器人、监控大屏 | 全部功能可用 |
| **M5: 联调测试完成** | 第16周末 | CRM集成完成、测试报告 | 全流程联调通过 |
| **M6: 试运行完成** | 第18周末 | 试运行报告 | 问题收敛，达到上线标准 |
| **M7: 正式上线** | 第19周 | 上线报告 | 系统平稳运行 |

### 8.4 详细进度计划

| 阶段 | 时间 | 主要任务 | 交付物 |
|------|------|---------|--------|
| **一、需求设计** | W1-W2 | 需求评审、架构设计、接口设计、详细设计 | 技术方案、接口文档、设计文档 |
| **二、环境搭建** | W3-W4 | 硬件采购到货、网络部署、FreeSWITCH安装、线路对接 | 基础环境就绪 |
| **三、核心开发** | W5-W10 | CTI中间件、API服务、坐席管理、ACD、录音、软电话条 | 核心功能可用 |
| **四、高级开发** | W11-W14 | 预测式外呼、机器人模块、IVR语音识别、监控大屏 | 高级功能可用 |
| **五、集成联调** | W15-W16 | CRM对接、全流程联调、高可用测试 | 联调通过 |
| **六、测试优化** | W16-W17 | 功能测试、性能测试、安全测试、Bug修复 | 测试报告 |
| **七、试运行** | W18 | 小范围上线、问题收集 | 试运行报告 |
| **八、正式上线** | W19 | 全面上线、监控保障 | 上线完成 |

---

## 第九章 项目组织架构

### 9.1 组织架构图

```
                        ┌─────────────────┐
                        │    项目发起人    │
                        │   (公司高层)     │
                        └────────┬────────┘
                                 │
                        ┌────────▼────────┐
                        │    项目经理      │
                        │   (总体负责)     │
                        └────────┬────────┘
                                 │
        ┌────────────────────────┼────────────────────────┐
        │                        │                        │
        ▼                        ▼                        ▼
┌───────────────┐      ┌───────────────┐      ┌───────────────┐
│   技术开发组   │      │   业务需求组   │      │   测试实施组   │
├───────────────┤      ├───────────────┤      ├───────────────┤
│ · 架构师      │      │ · 业务分析师  │      │ · 测试工程师  │
│ · 后端开发    │      │ · 产品经理    │      │ · 实施工程师  │
│ · 前端开发    │      │ · 业务对接人  │      │ · 运维工程师  │
│ · FreeSWITCH  │      │              │      │              │
└───────────────┘      └───────────────┘      └───────────────┘
```

### 9.2 角色职责

| 角色 | 人数 | 职责 |
|------|------|------|
| **项目发起人** | 1 | 项目决策、资源协调、重大问题决策 |
| **项目经理** | 1 | 项目整体管理、进度控制、风险管理、对外沟通 |
| **架构师** | 1 | 技术架构设计、技术选型、技术难点攻关 |
| **后端开发** | 3-4 | CTI中间件、API服务、外呼引擎、机器人模块开发 |
| **前端开发** | 1-2 | 软电话条、监控大屏开发 |
| **FreeSWITCH工程师** | 1-2 | FreeSWITCH配置、IVR开发、ESL对接 |
| **业务分析师** | 1 | 需求分析、业务流程梳理 |
| **测试工程师** | 1-2 | 测试用例、功能测试、性能测试 |
| **实施工程师** | 1 | 环境部署、系统上线 |
| **运维工程师** | 1 | 系统运维、监控告警 |
| **合计** | **12-16人** | |

### 9.3 沟通机制

| 会议类型 | 频率 | 参与人 | 内容 |
|---------|------|-------|------|
| **项目周会** | 每周一次 | 全体成员 | 进度汇报、问题协调、下周计划 |
| **每日站会** | 每日 | 开发+测试 | 当日任务、阻塞问题 |
| **技术评审会** | 按需 | 技术组 | 技术方案评审、代码评审 |
| **里程碑汇报** | 每里程碑 | 项目经理+发起人 | 阶段成果、风险汇报 |
| **上线评审会** | 上线前 | 全体+业务方 | 上线准备度检查 |

---

## 第十章 风险分析与应对

### 10.1 风险清单

| 风险编号 | 风险描述 | 可能性 | 影响程度 | 风险等级 |
|---------|---------|--------|---------|---------|
| R01 | FreeSWITCH技术门槛高，团队经验不足 | 高 | 高 | **高** |
| R02 | 运营商线路对接周期长 | 中 | 高 | **高** |
| R03 | CRM系统对接复杂度超预期 | 中 | 中 | **中** |
| R04 | ASR识别效果不及预期 | 中 | 中 | **中** |
| R05 | 硬件采购/到货延迟 | 低 | 高 | **中** |
| R06 | 并发性能不达标 | 低 | 高 | **中** |
| R07 | 关键人员离职 | 低 | 高 | **中** |
| R08 | 需求变更频繁 | 中 | 中 | **中** |

### 10.2 应对措施

| 风险编号 | 应对措施 | 责任人 |
|---------|---------|--------|
| **R01** | 1. 项目前期组织FreeSWITCH专项培训<br>2. 考虑引入外部FreeSWITCH顾问支持<br>3. 预留20%时间缓冲 | 项目经理 |
| **R02** | 1. 提前2个月启动线路申请<br>2. 准备备选运营商<br>3. 先用测试线路开发，并行对接正式线路 | 项目经理 |
| **R03** | 1. 需求阶段明确接口规范<br>2. 预留接口联调时间<br>3. CRM团队提前介入 | 架构师 |
| **R04** | 1. 提前进行ASR效果测试<br>2. 准备热词优化方案<br>3. 设计降级策略（按键兜底） | 技术组 |
| **R05** | 1. 提前下单采购<br>2. 准备备选供应商<br>3. 先用云主机开发 | 项目经理 |
| **R06** | 1. 上线前进行压力测试<br>2. 准备扩容方案<br>3. 架构支持水平扩展 | 架构师 |
| **R07** | 1. 知识文档化<br>2. 关键模块双人备份<br>3. 提前储备人员 | 项目经理 |
| **R08** | 1. 需求基线管理<br>2. 变更走评审流程<br>3. 评估变更影响后决策 | 产品经理 |

### 10.3 风险监控

| 风险等级 | 监控频率 | 汇报层级 |
|---------|---------|---------|
| **高** | 每周跟踪 | 项目发起人 |
| **中** | 双周跟踪 | 项目经理 |
| **低** | 月度跟踪 | 项目组 |

---

## 第十一章 结论与建议

### 11.1 结论

经过全面分析，本项目具备以下条件：

| 维度 | 评估结论 |
|------|---------|
| **必要性** | ✅ 业务发展迫切需要，现有模式已无法支撑 |
| **可行性** | ✅ 技术方案成熟，团队具备基本能力 |
| **经济性** | ✅ 投资回报期短（约4.8个月），效益显著 |
| **风险性** | ⚠️ 存在技术风险，但可通过措施控制 |

**综合结论：项目具备立项条件，建议批准立项。**

### 11.2 建议

1. **尽快启动**：业务增长迅速，建议尽快批准立项，启动实施
2. **技术准备**：建议在项目正式启动前，组织FreeSWITCH技术培训
3. **线路先行**：建议立即启动运营商线路申请，避免成为关键路径
4. **分步上线**：建议优先上线家政业务，验证后再推广至道路救援
5. **预留缓冲**：建议预留10%-20%预算和时间缓冲，应对不确定性
6. **运维同步**：建议运维人员全程参与，确保上线后平稳运行

### 11.3 审批意见

| 审批环节 | 审批人 | 日期 | 意见 |
|---------|-------|------|------|
| **技术评审** | | | |
| **财务评审** | | | |
| **业务评审** | | | |
| **最终审批** | | | |

---

## 附件清单

| 附件编号 | 附件名称 | 说明 |
|---------|---------|------|
| 附件1 | 《呼叫中心系统需求规格说明书》 | 详细需求文档 |
| 附件2 | 硬件选型参考清单 | 硬件详细配置及备选品牌 |
| 附件3 | 运营商线路资费对比 | 各运营商资费方案对比 |
| 附件4 | FreeSWITCH技术评估报告 | 技术可行性详细分析（如有） |

---

**文档结束**

---

以上是完整的项目立项书，涵盖了项目背景、建设方案、投资预算、效益分析、实施计划、组织架构和风险应对等内容。请您审阅，如需调整或补充，请随时告知！📋

### USER
能否提供FreeSWITCH的详细技术架构和ESL开发指南？

### ASSISTANT
# FreeSWITCH技术架构与ESL开发指南

---

## 目录

1. [FreeSWITCH技术架构](#第一部分-freeswitch技术架构)
2. [核心模块详解](#第二部分-核心模块详解)
3. [ESL开发指南](#第三部分-esl开发指南)
4. [呼叫中心场景开发实践](#第四部分-呼叫中心场景开发实践)
5. [高可用部署方案](#第五部分-高可用部署方案)
6. [性能优化指南](#第六部分-性能优化指南)

---

## 第一部分 FreeSWITCH技术架构

### 1.1 整体架构概览

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                           外部系统/应用层                                    │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐     │
│  │ CTI中间件 │  │ Web应用  │  │ 业务系统  │  │ 监控系统  │  │ 第三方App│     │
│  └─────┬────┘  └─────┬────┘  └─────┬────┘  └─────┬────┘  └─────┬────┘     │
│        │             │             │             │             │           │
│        └─────────────┴─────────────┴──────┬──────┴─────────────┘           │
│                                           │                                 │
│                                    ┌──────▼──────┐                         │
│                                    │  ESL接口    │                         │
│                                    │ (TCP:8021)  │                         │
│                                    └──────┬──────┘                         │
└───────────────────────────────────────────┼─────────────────────────────────┘
                                            │
┌───────────────────────────────────────────▼─────────────────────────────────┐
│                           FreeSWITCH 核心层                                  │
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │                        Core（核心引擎）                              │   │
│  │  ┌───────────────┐  ┌───────────────┐  ┌───────────────┐           │   │
│  │  │ 状态机管理     │  │ 内存管理      │  │ 线程池管理    │           │   │
│  │  └───────────────┘  └───────────────┘  └───────────────┘           │   │
│  │  ┌───────────────┐  ┌───────────────┐  ┌───────────────┐           │   │
│  │  │ 事件系统      │  │ 数据库抽象    │  │ 日志系统      │           │   │
│  │  └───────────────┘  └───────────────┘  └───────────────┘           │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │                      Module Interface（模块接口层）                  │   │
│  │                                                                     │   │
│  │  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐   │   │
│  │  │ Endpoint    │ │ Application │ │  Codec      │ │  Dialplan   │   │   │
│  │  │ 终端模块    │ │ 应用模块    │ │  编解码     │ │  拨号计划   │   │   │
│  │  └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘   │   │
│  │  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐   │   │
│  │  │ Event       │ │ Format      │ │  Logger     │ │  XML        │   │   │
│  │  │ 事件模块    │ │ 文件格式    │ │  日志模块   │ │  XML处理    │   │   │
│  │  └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘   │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
                                            │
┌───────────────────────────────────────────▼─────────────────────────────────┐
│                            协议/媒体层                                       │
│                                                                             │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐          │
│  │  mod_sofia  │ │ mod_verto   │ │ mod_rtmp    │ │ mod_skinny  │          │
│  │  (SIP协议)  │ │ (WebRTC)    │ │ (RTMP)      │ │ (Cisco)     │          │
│  └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘          │
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │                        媒体处理引擎                                  │   │
│  │  ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐           │   │
│  │  │ RTP处理   │ │ 编解码转换 │ │ 录音处理  │ │ DTMF检测  │           │   │
│  │  └───────────┘ └───────────┘ └───────────┘ └───────────┘           │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
                                            │
┌───────────────────────────────────────────▼─────────────────────────────────┐
│                              网络层                                          │
│       SIP/UDP/TCP          RTP/SRTP           WebSocket           HTTP      │
│         :5060               :16384-32768        :8081              :8080    │
└─────────────────────────────────────────────────────────────────────────────┘
```

### 1.2 核心概念

#### 1.2.1 Channel（通道）

Channel是FreeSWITCH中最核心的概念，代表一路通话的端点。

```
┌─────────────────────────────────────────────────────────────────┐
│                         一通电话                                 │
│                                                                 │
│    ┌─────────────┐          Bridge           ┌─────────────┐   │
│    │  Channel A  │◄─────────────────────────►│  Channel B  │   │
│    │  (主叫方)    │                           │  (被叫方)    │   │
│    │             │                           │             │   │
│    │ UUID: xxx-a │                           │ UUID: xxx-b │   │
│    │ State: CS_  │                           │ State: CS_  │   │
│    │ Variables   │                           │ Variables   │   │
│    └─────────────┘                           └─────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

**Channel状态机：**

```
                    ┌──────────────────────────────────────────┐
                    │                                          │
                    ▼                                          │
┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐    ┌──┴──────┐
│  NEW    │───►│ INIT    │───►│ ROUTING │───►│ EXECUTE │───►│ HANGUP  │
└─────────┘    └─────────┘    └─────────┘    └─────────┘    └─────────┘
                                   │              │
                                   │              │
                              ┌────▼────┐    ┌────▼────┐
                              │ CONSUME │    │ EXCHANGE│
                              │ MEDIA   │    │ MEDIA   │
                              └─────────┘    └─────────┘

Channel状态说明：
├── CS_NEW        : 新建通道
├── CS_INIT       : 初始化
├── CS_ROUTING    : 路由中（执行dialplan）
├── CS_EXECUTE    : 执行应用中
├── CS_EXCHANGE_MEDIA : 媒体交换中（bridged）
├── CS_CONSUME_MEDIA  : 媒体消费中
├── CS_HIBERNATE  : 休眠状态
├── CS_HANGUP     : 挂断中
└── CS_DESTROY    : 销毁
```

#### 1.2.2 Session（会话）

Session包含Channel及其相关上下文信息：

```c
// Session结构概念图
Session {
    ├── Channel              // 通道信息
    │   ├── UUID            // 唯一标识
    │   ├── State           // 当前状态
    │   ├── Direction       // 呼叫方向(inbound/outbound)
    │   └── Variables       // 通道变量
    │
    ├── Caller Profile      // 主叫信息
    │   ├── caller_id_name
    │   ├── caller_id_number
    │   ├── destination_number
    │   └── ...
    │
    ├── Codec               // 编解码信息
    ├── RTP Session         // RTP会话
    └── Bridge Session      // 桥接会话（如果有）
}
```

#### 1.2.3 Event（事件）

FreeSWITCH基于事件驱动架构，所有状态变化都产生事件：

```
┌─────────────────────────────────────────────────────────────────┐
│                     FreeSWITCH Event System                     │
│                                                                 │
│  ┌───────────────┐                                             │
│  │ Event Producer│ ──────┐                                     │
│  │ (模块/核心)    │       │                                     │
│  └───────────────┘       │      ┌─────────────────────────┐   │
│                          ├─────►│    Event Dispatcher     │   │
│  ┌───────────────┐       │      │       (事件分发器)       │   │
│  │ Event Producer│ ──────┤      └───────────┬─────────────┘   │
│  │ (模块/核心)    │       │                  │                 │
│  └───────────────┘       │      ┌───────────┴─────────────┐   │
│                          │      │           │             │   │
│  ┌───────────────┐       │      ▼           ▼             ▼   │
│  │ Event Producer│ ──────┘  ┌───────┐  ┌───────┐    ┌───────┐│
│  │ (模块/核心)    │          │ ESL   │  │Internal│    │ Lua/  ││
│  └───────────────┘          │Consumer│  │Handler │    │ JS    ││
│                             └───────┘  └───────┘    └───────┘│
└─────────────────────────────────────────────────────────────────┘
```

**常用事件类型：**

| 事件名称 | 触发时机 | 用途 |
|---------|---------|------|
| CHANNEL_CREATE | 通道创建 | 新呼叫进入 |
| CHANNEL_ANSWER | 通话接听 | 通话建立 |
| CHANNEL_BRIDGE | 通道桥接 | 双方接通 |
| CHANNEL_UNBRIDGE | 取消桥接 | 桥接断开 |
| CHANNEL_HANGUP | 挂断 | 通话结束 |
| CHANNEL_HANGUP_COMPLETE | 挂断完成 | 通话完全结束 |
| CHANNEL_STATE | 状态变化 | 状态监控 |
| DTMF | 按键检测 | IVR交互 |
| RECORD_START | 开始录音 | 录音控制 |
| RECORD_STOP | 停止录音 | 录音控制 |
| CUSTOM | 自定义事件 | 业务扩展 |

### 1.3 目录结构

```
/usr/local/freeswitch/
├── bin/                          # 可执行文件
│   └── freeswitch               # 主程序
│
├── conf/                         # 配置文件目录
│   ├── freeswitch.xml           # 主配置文件
│   ├── vars.xml                 # 全局变量
│   ├── sip_profiles/            # SIP配置
│   │   ├── internal.xml         # 内部Profile（坐席注册）
│   │   └── external.xml         # 外部Profile（中继对接）
│   ├── dialplan/                # 拨号计划
│   │   ├── default.xml          # 默认拨号计划
│   │   └── public.xml           # 公共拨号计划（呼入）
│   ├── directory/               # 用户目录（分机注册）
│   │   └── default/
│   │       └── 1000.xml         # 分机1000配置
│   ├── autoload_configs/        # 模块自动加载配置
│   │   ├── modules.conf.xml     # 模块加载列表
│   │   ├── sofia.conf.xml       # SIP模块配置
│   │   ├── event_socket.conf.xml# ESL配置
│   │   └── ...
│   └── lang/                    # 语音提示
│
├── lib/                         # 库文件
├── mod/                         # 模块文件(.so)
├── log/                         # 日志目录
├── db/                          # 数据库文件
├── recordings/                  # 录音文件
├── sounds/                      # 语音文件
├── scripts/                     # 脚本目录
└── storage/                     # 存储目录
```

---

## 第二部分 核心模块详解

### 2.1 mod_sofia（SIP模块）

mod_sofia是FreeSWITCH的SIP协议栈实现，基于Sofia-SIP库。

#### 2.1.1 架构

```
┌─────────────────────────────────────────────────────────────────────┐
│                          mod_sofia                                   │
│                                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                     Sofia-SIP Stack                          │   │
│  │  ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐    │   │
│  │  │    NUA    │ │    NTA    │ │   TPORT   │ │    SDP    │    │   │
│  │  │ User Agent│ │Transaction│ │ Transport │ │  Parser   │    │   │
│  │  └───────────┘ └───────────┘ └───────────┘ └───────────┘    │   │
│  └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
│  ┌─────────────────────────────────────────────────────────────┐   │
│  │                      Profile Management                      │   │
│  │                                                             │   │
│  │  ┌─────────────────────┐    ┌─────────────────────┐        │   │
│  │  │   Internal Profile  │    │   External Profile  │        │   │
│  │  │   (坐席/分机注册)    │    │   (中继/网关)        │        │   │
│  │  │   UDP:5060          │    │   UDP:5080          │        │   │
│  │  └─────────────────────┘    └─────────────────────┘        │   │
│  │                                                             │   │
│  │  ┌─────────────────────────────────────────────────────┐   │   │
│  │  │                    Gateway Management                │   │   │
│  │  │  ┌─────────┐  ┌─────────┐  ┌─────────┐             │   │   │
│  │  │  │Gateway A│  │Gateway B│  │Gateway C│  ...        │   │   │
│  │  │  │(运营商1) │  │(运营商2) │  │(备用)   │             │   │   │
│  │  │  └─────────┘  └─────────┘  └─────────┘             │   │   │
│  │  └─────────────────────────────────────────────────────┘   │   │
│  └─────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────┘
```

#### 2.1.2 配置示例

**Internal Profile（坐席注册）：**

```xml
<!-- conf/sip_profiles/internal.xml -->
<profile name="internal">
  <settings>
    <!-- 监听地址和端口 -->
    <param name="sip-ip" value="$${local_ip_v4}"/>
    <param name="sip-port" value="5060"/>
    <param name="rtp-ip" value="$${local_ip_v4}"/>
    
    <!-- 编解码 -->
    <param name="inbound-codec-prefs" value="PCMA,PCMU,G729"/>
    <param name="outbound-codec-prefs" value="PCMA,PCMU,G729"/>
    
    <!-- RTP端口范围 -->
    <param name="rtp-start-port" value="16384"/>
    <param name="rtp-end-port" value="32768"/>
    
    <!-- 注册相关 -->
    <param name="accept-blind-reg" value="false"/>
    <param name="accept-blind-auth" value="false"/>
    
    <!-- NAT穿透 -->
    <param name="local-network-acl" value="localnet.auto"/>
    <param name="apply-nat-acl" value="nat.auto"/>
    
    <!-- 媒体相关 -->
    <param name="inbound-late-negotiation" value="true"/>
    <param name="inbound-zrtp-passthru" value="true"/>
    
    <!-- 安全相关 -->
    <param name="auth-calls" value="true"/>
    <param name="log-auth-failures" value="true"/>
  </settings>
</profile>
```

**External Profile（中继对接）：**

```xml
<!-- conf/sip_profiles/external.xml -->
<profile name="external">
  <gateways>
    <!-- 运营商SIP中继 -->
    <gateway name="carrier_primary">
      <param name="username" value="your_username"/>
      <param name="password" value="your_password"/>
      <param name="realm" value="sip.carrier.com"/>
      <param name="proxy" value="sip.carrier.com"/>
      <param name="register" value="true"/>
      <param name="retry-seconds" value="30"/>
      <param name="caller-id-in-from" value="true"/>
    </gateway>
    
    <!-- 备用中继 -->
    <gateway name="carrier_backup">
      <param name="username" value="backup_username"/>
      <param name="password" value="backup_password"/>
      <param name="realm" value="sip.backup-carrier.com"/>
      <param name="proxy" value="sip.backup-carrier.com"/>
      <param name="register" value="true"/>
    </gateway>
  </gateways>
  
  <settings>
    <param name="sip-ip" value="$${local_ip_v4}"/>
    <param name="sip-port" value="5080"/>
    <param name="rtp-ip" value="$${local_ip_v4}"/>
    <param name="context" value="public"/>
  </settings>
</profile>
```

### 2.2 Dialplan（拨号计划）

拨号计划定义了呼叫的路由和处理逻辑。

#### 2.2.1 执行流程

```
┌──────────────────────────────────────────────────────────────────┐
│                    Dialplan 执行流程                              │
│                                                                  │
│  来电进入                                                        │
│     │                                                            │
│     ▼                                                            │
│  ┌─────────────────────────────────────────────────────────────┐│
│  │  Context 选择                                                ││
│  │  (根据Profile配置的context)                                  ││
│  │  例如: public (呼入) / default (内部)                        ││
│  └──────────────────────────┬──────────────────────────────────┘│
│                             │                                    │
│                             ▼                                    │
│  ┌─────────────────────────────────────────────────────────────┐│
│  │  Extension 匹配                                              ││
│  │  (按顺序匹配condition)                                       ││
│  │                                                              ││
│  │  Extension 1: ──┐                                            ││
│  │    Condition ───┼─► 不匹配 ───► 下一个Extension              ││
│  │                 │                                            ││
│  │                 └─► 匹配 ───► 执行Actions                    ││
│  └──────────────────────────┬──────────────────────────────────┘│
│                             │                                    │
│                             ▼                                    │
│  ┌─────────────────────────────────────────────────────────────┐│
│  │  Action 执行                                                 ││
│  │  (按顺序执行应用)                                            ││
│  │                                                              ││
│  │  action: answer                                              ││
│  │  action: playback                                            ││
│  │  action: bridge                                              ││
│  │  ...                                                         ││
│  └─────────────────────────────────────────────────────────────┘│
│                                                                  │
└──────────────────────────────────────────────────────────────────┘
```

#### 2.2.2 配置示例

**呼入路由（public.xml）：**

```xml
<!-- conf/dialplan/public.xml -->
<context name="public">
  
  <!-- 400号码呼入 - 道路救援 -->
  <extension name="inbound_rescue">
    <condition field="destination_number" expression="^(4001234561)$">
      <!-- 设置业务线标识 -->
      <action application="set" data="business_type=rescue"/>
      <action application="set" data="skill_group=rescue_group"/>
      
      <!-- 进入IVR -->
      <action application="transfer" data="main_ivr XML default"/>
    </condition>
  </extension>
  
  <!-- 400号码呼入 - 家政服务 -->
  <extension name="inbound_housekeeping">
    <condition field="destination_number" expression="^(4001234562)$">
      <action application="set" data="business_type=housekeeping"/>
      <action application="set" data="skill_group=housekeeping_group"/>
      <action application="transfer" data="main_ivr XML default"/>
    </condition>
  </extension>
  
  <!-- 400号码呼入 - 代驾服务 -->
  <extension name="inbound_designated_driver">
    <condition field="destination_number" expression="^(4001234563)$">
      <action application="set" data="business_type=driver"/>
      <action application="set" data="skill_group=driver_group"/>
      <action application="transfer" data="main_ivr XML default"/>
    </condition>
  </extension>
  
</context>
```

**IVR和呼叫处理（default.xml）：**

```xml
<!-- conf/dialplan/default.xml -->
<context name="default">

  <!-- 主IVR入口 -->
  <extension name="main_ivr">
    <condition field="destination_number" expression="^main_ivr$">
      <action application="answer"/>
      <action application="sleep" data="500"/>
      
      <!-- 播放欢迎语 -->
      <action application="playback" data="ivr/welcome.wav"/>
      
      <!-- 语音识别或按键导航 -->
      <action application="play_and_get_digits" 
              data="1 1 3 5000 # ivr/menu_prompt.wav ivr/invalid.wav 
                    ivr_selection \d 3000"/>
      
      <!-- 根据按键转接 -->
      <action application="transfer" data="route_by_selection"/>
    </condition>
  </extension>
  
  <!-- 按键路由 -->
  <extension name="route_by_selection">
    <condition field="destination_number" expression="^route_by_selection$">
      
      <!-- 按1转道路救援 -->
      <condition field="${ivr_selection}" expression="^1$">
        <action application="set" data="skill_group=rescue_group"/>
        <action application="transfer" data="queue_call"/>
      </condition>
      
      <!-- 按2转家政服务 -->
      <condition field="${ivr_selection}" expression="^2$">
        <action application="set" data="skill_group=housekeeping_group"/>
        <action application="transfer" data="queue_call"/>
      </condition>
      
      <!-- 按0转人工 -->
      <condition field="${ivr_selection}" expression="^0$">
        <action application="transfer" data="queue_call"/>
      </condition>
      
    </condition>
  </extension>
  
  <!-- 排队等待（ACD由ESL处理） -->
  <extension name="queue_call">
    <condition field="destination_number" expression="^queue_call$">
      <!-- 通知ESL有新呼叫需要排队 -->
      <action application="set" data="api_hangup_hook=uuid_broadcast ${uuid} 
              queue_exit.wav both"/>
      
      <!-- 播放等待音乐，等待ESL分配坐席 -->
      <action application="playback" data="local_stream://moh"/>
    </condition>
  </extension>
  
  <!-- 坐席外呼 -->
  <extension name="agent_outbound">
    <condition field="destination_number" expression="^9(\d{11})$">
      <action application="set" data="effective_caller_id_number=
              ${outbound_caller_id}"/>
      <action application="set" data="effective_caller_id_name=公司名称"/>
      
      <!-- 录音 -->
      <action application="set" data="RECORD_STEREO=true"/>
      <action application="set" data="recording_path=/recordings/${strftime(%Y/%m/%d)}"/>
      <action application="record_session" data="${recording_path}/${uuid}.wav"/>
      
      <!-- 通过网关外呼 -->
      <action application="bridge" data="sofia/gateway/carrier_primary/$1"/>
    </condition>
  </extension>
  
  <!-- 坐席互转 -->
  <extension name="transfer_to_agent">
    <condition field="destination_number" expression="^(\d{4})$">
      <action application="bridge" data="user/$1@${domain_name}"/>
    </condition>
  </extension>

</context>
```

### 2.3 mod_event_socket（ESL模块）

ESL是FreeSWITCH对外提供的控制接口，是开发呼叫中心的核心。

#### 2.3.1 工作模式

```
┌─────────────────────────────────────────────────────────────────────┐
│                        ESL 工作模式                                  │
│                                                                     │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │                    Inbound 模式                                │ │
│  │                    (客户端主动连接)                            │ │
│  │                                                               │ │
│  │   ┌───────────┐         TCP:8021        ┌──────────────────┐ │ │
│  │   │ CTI中间件  │ ──────────────────────► │   FreeSWITCH     │ │ │
│  │   │ (Client)  │ ◄────────────────────── │   (Server)       │ │ │
│  │   └───────────┘     Event Stream        └──────────────────┘ │ │
│  │                                                               │ │
│  │   特点：                                                      │ │
│  │   - 客户端主动连接FreeSWITCH                                 │ │
│  │   - 可以订阅事件、发送命令                                    │ │
│  │   - 适合集中控制、监控场景                                    │ │
│  │   - 推荐呼叫中心使用此模式                                    │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │                    Outbound 模式                               │ │
│  │                    (FreeSWITCH主动连接)                        │ │
│  │                                                               │ │
│  │   ┌──────────────────┐        TCP         ┌───────────────┐  │ │
│  │   │   FreeSWITCH     │ ──────────────────►│  应用服务器    │  │ │
│  │   │   (每个呼叫)     │ ◄────────────────── │  (Server)     │  │ │
│  │   └──────────────────┘                    └───────────────┘  │ │
│  │                                                               │ │
│  │   特点：                                                      │ │
│  │   - 每个呼叫FreeSWITCH主动连接应用                           │ │
│  │   - 在dialplan中使用socket应用触发                           │ │
│  │   - 适合简单IVR场景                                          │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘
```

#### 2.3.2 ESL配置

```xml
<!-- conf/autoload_configs/event_socket.conf.xml -->
<configuration name="event_socket.conf" description="Socket Client">
  <settings>
    <!-- 监听地址（生产环境建议限制IP） -->
    <param name="nat-map" value="false"/>
    <param name="listen-ip" value="0.0.0.0"/>
    <param name="listen-port" value="8021"/>
    
    <!-- 认证密码 -->
    <param name="password" value="your_secure_password"/>
    
    <!-- ACL限制（可选） -->
    <!-- <param name="apply-inbound-acl" value="lan"/> -->
  </settings>
</configuration>
```

---

## 第三部分 ESL开发指南

### 3.1 ESL协议详解

#### 3.1.1 协议格式

ESL使用类HTTP的文本协议：

```
┌─────────────────────────────────────────────────────────────────────┐
│                       ESL 消息格式                                   │
│                                                                     │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │  请求格式:                                                     │ │
│  │  ┌─────────────────────────────────────────────────────────┐  │ │
│  │  │ command [arguments]                                      │  │ │
│  │  │ Header-Name: header-value                                │  │ │
│  │  │ Header-Name: header-value                                │  │ │
│  │  │                                                          │  │ │
│  │  │ [body if Content-Length > 0]                             │  │ │
│  │  └─────────────────────────────────────────────────────────┘  │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │  响应格式:                                                     │ │
│  │  ┌─────────────────────────────────────────────────────────┐  │ │
│  │  │ Content-Type: text/event-plain                           │  │ │
│  │  │ Content-Length: 1234                                     │  │ │
│  │  │ Reply-Text: +OK [message]                                │  │ │
│  │  │                                                          │  │ │
│  │  │ [body]                                                   │  │ │
│  │  └─────────────────────────────────────────────────────────┘  │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘
```

#### 3.1.2 常用命令

| 命令类型 | 命令 | 说明 |
|---------|------|------|
| **认证** | `auth <password>` | 连接认证 |
| **事件订阅** | `event plain <event_types>` | 订阅事件 |
| | `event plain ALL` | 订阅所有事件 |
| | `event plain CHANNEL_CREATE CHANNEL_ANSWER CHANNEL_HANGUP` | 订阅指定事件 |
| | `nixevent <event_type>` | 取消订阅 |
| | `noevents` | 取消所有订阅 |
| **API命令** | `api <command> [args]` | 同步执行API |
| | `bgapi <command> [args]` | 异步执行API |
| **过滤器** | `filter <header> <value>` | 过滤特定事件 |
| | `filter Unique-ID <uuid>` | 只接收指定UUID事件 |
| **呼叫控制** | `sendmsg <uuid>` | 向指定通道发送消息 |
| | `execute` | 在通道上执行应用 |
| **其他** | `log <level>` | 设置日志级别 |
| | `linger` | 保持连接 |
| | `nolinger` | 取消保持 |
| | `exit` | 断开连接 |

### 3.2 Java ESL开发

#### 3.2.1 项目结构

```
callcenter-cti/
├── pom.xml
├── src/main/java/
│   └── com/callcenter/
│       ├── Application.java
│       ├── esl/
│       │   ├── EslConnectionManager.java       # ESL连接管理
│       │   ├── EslEventHandler.java            # 事件处理
│       │   ├── EslCommandExecutor.java         # 命令执行
│       │   └── InboundClient.java              # Inbound客户端
│       ├── service/
│       │   ├── AgentService.java               # 坐席服务
│       │   ├── CallService.java                # 呼叫服务
│       │   ├── QueueService.java               # 排队服务
│       │   └── AcdService.java                 # 话务分配服务
│       ├── model/
│       │   ├── Agent.java                      # 坐席模型
│       │   ├── Call.java                       # 呼叫模型
│       │   └── Queue.java                      # 队列模型
│       ├── api/
│       │   ├── AgentController.java            # 坐席API
│       │   └── CallController.java             # 呼叫API
│       └── websocket/
│           └── EventPushHandler.java           # 事件推送
└── src/main/resources/
    └── application.yml
```

#### 3.2.2 Maven依赖

```xml
<!-- pom.xml -->
<dependencies>
    <!-- Spring Boot -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    
    <!-- FreeSWITCH ESL Client -->
    <dependency>
        <groupId>org.freeswitch.esl.client</groupId>
        <artifactId>org.freeswitch.esl.client</artifactId>
        <version>0.9.2</version>
    </dependency>
    
    <!-- Netty (ESL底层通信) -->
    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.86.Final</version>
    </dependency>
    
    <!-- Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
    <!-- JSON处理 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>2.0.23</version>
    </dependency>
    
    <!-- 工具类 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>
```

#### 3.2.3 ESL连接管理

```java
package com.callcenter.esl;

import lombok.extern.slf4j.Slf4j;
import org.freeswitch.esl.client.inbound.Client;
import org.freeswitch.esl.client.inbound.InboundConnectionFailure;
import org.freeswitch.esl.client.transport.event.EslEvent;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * ESL连接管理器
 * 负责与FreeSWITCH建立和维护连接
 */
@Slf4j
@Component
public class EslConnectionManager {

    @Value("${freeswitch.esl.host:127.0.0.1}")
    private String eslHost;

    @Value("${freeswitch.esl.port:8021}")
    private int eslPort;

    @Value("${freeswitch.esl.password:ClueCon}")
    private String eslPassword;

    @Value("${freeswitch.esl.timeout:30}")
    private int timeout;

    private Client eslClient;
    private final AtomicBoolean connected = new AtomicBoolean(false);
    private final AtomicBoolean reconnecting = new AtomicBoolean(false);
    
    private ScheduledExecutorService scheduler;
    private ExecutorService eventExecutor;
    
    private EslEventHandler eventHandler;

    @PostConstruct
    public void init() {
        // 初始化线程池
        scheduler = Executors.newSingleThreadScheduledExecutor(
            r -> new Thread(r, "esl-reconnect"));
        eventExecutor = new ThreadPoolExecutor(
            4, 16, 60L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(10000),
            r -> new Thread(r, "esl-event-handler"),
            new ThreadPoolExecutor.CallerRunsPolicy()
        );
        
        // 建立连接
        connect();
        
        // 启动心跳检测
        startHeartbeat();
    }

    /**
     * 建立ESL连接
     */
    public synchronized void connect() {
        if (connected.get()) {
            log.info("ESL已连接，无需重复连接");
            return;
        }

        try {
            log.info("正在连接FreeSWITCH ESL: {}:{}", eslHost, eslPort);
            
            eslClient = new Client();
            eslClient.connect(eslHost, eslPort, eslPassword, timeout);
            
            // 订阅事件
            subscribeEvents();
            
            // 设置事件监听器
            eslClient.setEventSubscriptions("plain", "all");
            eslClient.addEventListener((ctx, event) -> {
                handleEvent(event);
            });
            
            connected.set(true);
            reconnecting.set(false);
            log.info("ESL连接成功");
            
        } catch (InboundConnectionFailure e) {
            log.error("ESL连接失败: {}", e.getMessage());
            connected.set(false);
            scheduleReconnect();
        }
    }

    /**
     * 订阅所需事件
     */
    private void subscribeEvents() {
        String events = String.join(" ",
            "CHANNEL_CREATE",
            "CHANNEL_ANSWER", 
            "CHANNEL_BRIDGE",
            "CHANNEL_UNBRIDGE",
            "CHANNEL_HANGUP",
            "CHANNEL_HANGUP_COMPLETE",
            "CHANNEL_STATE",
            "DTMF",
            "RECORD_START",
            "RECORD_STOP",
            "CUSTOM"
        );
        
        eslClient.setEventSubscriptions("plain", events);
        eslClient.addEventFilter("Event-Name", "CHANNEL_CREATE");
        log.info("已订阅事件: {}", events);
    }

    /**
     * 处理事件
     */
    private void handleEvent(EslEvent event) {
        eventExecutor.submit(() -> {
            try {
                if (eventHandler != null) {
                    eventHandler.handle(event);
                }
            } catch (Exception e) {
                log.error("事件处理异常: {}", e.getMessage(), e);
            }
        });
    }

    /**
     * 启动心跳检测
     */
    private void startHeartbeat() {
        scheduler.scheduleAtFixedRate(() -> {
            if (connected.get()) {
                try {
                    // 发送status命令检测连接
                    eslClient.sendSyncApiCommand("status", "");
                } catch (Exception e) {
                    log.warn("心跳检测失败，连接可能已断开");
                    connected.set(false);
                    scheduleReconnect();
                }
            }
        }, 30, 30, TimeUnit.SECONDS);
    }

    /**
     * 计划重连
     */
    private void scheduleReconnect() {
        if (reconnecting.compareAndSet(false, true)) {
            scheduler.schedule(() -> {
                log.info("尝试重新连接ESL...");
                connect();
            }, 5, TimeUnit.SECONDS);
        }
    }

    /**
     * 同步执行API命令
     */
    public String sendSyncApiCommand(String command, String args) {
        checkConnection();
        try {
            return eslClient.sendSyncApiCommand(command, args).getBodyLines().get(0);
        } catch (Exception e) {
            log.error("执行API命令失败: {} {}", command, args, e);
            throw new RuntimeException("ESL命令执行失败", e);
        }
    }

    /**
     * 异步执行API命令
     */
    public CompletableFuture<String> sendAsyncApiCommand(String command, String args) {
        return CompletableFuture.supplyAsync(() -> 
            sendSyncApiCommand(command, args), eventExecutor);
    }

    /**
     * 向指定UUID发送消息执行应用
     */
    public void sendExecute(String uuid, String application, String args) {
        checkConnection();
        try {
            eslClient.sendSyncApiCommand("uuid_broadcast", 
                uuid + " " + application + "::" + args + " both");
        } catch (Exception e) {
            log.error("发送Execute失败: uuid={}, app={}", uuid, application, e);
        }
    }

    /**
     * 检查连接状态
     */
    private void checkConnection() {
        if (!connected.get()) {
            throw new RuntimeException("ESL未连接");
        }
    }

    /**
     * 获取连接状态
     */
    public boolean isConnected() {
        return connected.get();
    }

    /**
     * 设置事件处理器
     */
    public void setEventHandler(EslEventHandler handler) {
        this.eventHandler = handler;
    }

    @PreDestroy
    public void destroy() {
        try {
            connected.set(false);
            if (eslClient != null) {
                eslClient.close();
            }
            if (scheduler != null) {
                scheduler.shutdown();
            }
            if (eventExecutor != null) {
                eventExecutor.shutdown();
            }
            log.info("ESL连接已关闭");
        } catch (Exception e) {
            log.error("关闭ESL连接异常", e);
        }
    }
}
```

#### 3.2.4 事件处理器

```java
package com.callcenter.esl;

import com.callcenter.model.Call;
import com.callcenter.model.CallDirection;
import com.callcenter.model.CallState;
import com.callcenter.service.AcdService;
import com.callcenter.service.CallService;
import com.callcenter.websocket.EventPushService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.freeswitch.esl.client.transport.event.EslEvent;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.Map;

/**
 * ESL事件处理器
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class EslEventHandler {

    private final CallService callService;
    private final AcdService acdService;
    private final EventPushService eventPushService;

    /**
     * 处理ESL事件
     */
    public void handle(EslEvent event) {
        String eventName = event.getEventName();
        Map<String, String> headers = event.getEventHeaders();
        
        log.debug("收到事件: {}, UUID: {}", eventName, headers.get("Unique-ID"));

        switch (eventName) {
            case "CHANNEL_CREATE":
                handleChannelCreate(headers);
                break;
            case "CHANNEL_ANSWER":
                handleChannelAnswer(headers);
                break;
            case "CHANNEL_BRIDGE":
                handleChannelBridge(headers);
                break;
            case "CHANNEL_UNBRIDGE":
                handleChannelUnbridge(headers);
                break;
            case "CHANNEL_HANGUP":
                handleChannelHangup(headers);
                break;
            case "CHANNEL_HANGUP_COMPLETE":
                handleChannelHangupComplete(headers);
                break;
            case "DTMF":
                handleDtmf(headers);
                break;
            case "RECORD_START":
                handleRecordStart(headers);
                break;
            case "RECORD_STOP":
                handleRecordStop(headers);
                break;
            case "CUSTOM":
                handleCustomEvent(headers);
                break;
            default:
                log.trace("未处理的事件类型: {}", eventName);
        }
    }

    /**
     * 处理通道创建事件（新呼叫）
     */
    private void handleChannelCreate(Map<String, String> headers) {
        String uuid = headers.get("Unique-ID");
        String direction = headers.get("Call-Direction");
        String callerNumber = headers.get("Caller-Caller-ID-Number");
        String calleeNumber = headers.get("Caller-Destination-Number");
        
        log.info("新呼叫: uuid={}, direction={}, caller={}, callee={}", 
                uuid, direction, callerNumber, calleeNumber);

        // 创建呼叫记录
        Call call = new Call();
        call.setUuid(uuid);
        call.setDirection("inbound".equals(direction) ? 
            CallDirection.INBOUND : CallDirection.OUTBOUND);
        call.setCallerNumber(callerNumber);
        call.setCalleeNumber(calleeNumber);
        call.setState(CallState.RINGING);
        call.setCreateTime(LocalDateTime.now());
        
        // 获取业务标识（从通道变量）
        String businessType = headers.get("variable_business_type");
        String skillGroup = headers.get("variable_skill_group");
        call.setBusinessType(businessType);
        call.setSkillGroup(skillGroup);
        
        callService.saveCall(call);

        // 如果是呼入，触发ACD排队
        if ("inbound".equals(direction)) {
            acdService.enqueue(call);
        }

        // 推送事件到前端
        eventPushService.pushCallEvent("CALL_RINGING", call);
    }

    /**
     * 处理通话接听事件
     */
    private void handleChannelAnswer(Map<String, String> headers) {
        String uuid = headers.get("Unique-ID");
        
        log.info("通话接听: uuid={}", uuid);

        Call call = callService.getCallByUuid(uuid);
        if (call != null) {
            call.setState(CallState.ANSWERED);
            call.setAnswerTime(LocalDateTime.now());
            callService.updateCall(call);
            
            // 推送事件
            eventPushService.pushCallEvent("CALL_ANSWERED", call);
        }
    }

    /**
     * 处理通道桥接事件（双方接通）
     */
    private void handleChannelBridge(Map<String, String> headers) {
        String uuid = headers.get("Unique-ID");
        String otherUuid = headers.get("Other-Leg-Unique-ID");
        
        log.info("通道桥接: uuid={}, otherUuid={}", uuid, otherUuid);

        Call call = callService.getCallByUuid(uuid);
        if (call != null) {
            call.setState(CallState.BRIDGED);
            call.setBridgeUuid(otherUuid);
            call.setBridgeTime(LocalDateTime.now());
            callService.updateCall(call);
            
            // 推送事件
            eventPushService.pushCallEvent("CALL_BRIDGED", call);
        }
    }

    /**
     * 处理取消桥接事件
     */
    private void handleChannelUnbridge(Map<String, String> headers) {
        String uuid = headers.get("Unique-ID");
        
        log.info("取消桥接: uuid={}", uuid);

        Call call = callService.getCallByUuid(uuid);
        if (call != null) {
            call.setState(CallState.UNBRIDGED);
            callService.updateCall(call);
            
            eventPushService.pushCallEvent("CALL_UNBRIDGED", call);
        }
    }

    /**
     * 处理挂断事件
     */
    private void handleChannelHangup(Map<String, String> headers) {
        String uuid = headers.get("Unique-ID");
        String hangupCause = headers.get("Hangup-Cause");
        
        log.info("通话挂断: uuid={}, cause={}", uuid, hangupCause);

        Call call = callService.getCallByUuid(uuid);
        if (call != null) {
            call.setState(CallState.HANGUP);
            call.setHangupCause(hangupCause);
            call.setHangupTime(LocalDateTime.now());
            
            // 计算通话时长
            if (call.getAnswerTime() != null) {
                long duration = java.time.Duration.between(
                    call.getAnswerTime(), call.getHangupTime()).getSeconds();
                call.setDuration(duration);
            }
            
            callService.updateCall(call);
            
            // 推送事件
            eventPushService.pushCallEvent("CALL_HANGUP", call);
            
            // 更新坐席状态
            if (call.getAgentId() != null) {
                acdService.onCallEnd(call.getAgentId(), call);
            }
        }
    }

    /**
     * 处理挂断完成事件（获取完整话单）
     */
    private void handleChannelHangupComplete(Map<String, String> headers) {
        String uuid = headers.get("Unique-ID");
        String billsec = headers.get("variable_billsec");
        String duration = headers.get("variable_duration");
        String recordingPath = headers.get("variable_recording_path");
        
        log.info("通话结束: uuid={}, billsec={}, recording={}", 
                uuid, billsec, recordingPath);

        Call call = callService.getCallByUuid(uuid);
        if (call != null) {
            call.setBillsec(billsec != null ? Long.parseLong(billsec) : 0);
            call.setRecordingPath(recordingPath);
            callService.updateCall(call);
            
            // 推送话单
            eventPushService.pushCdr(call);
        }
    }

    /**
     * 处理DTMF按键事件
     */
    private void handleDtmf(Map<String, String> headers) {
        String uuid = headers.get("Unique-ID");
        String digit = headers.get("DTMF-Digit");
        
        log.info("DTMF按键: uuid={}, digit={}", uuid, digit);

        // 推送按键事件
        eventPushService.pushDtmf(uuid, digit);
    }

    /**
     * 处理录音开始事件
     */
    private void handleRecordStart(Map<String, String> headers) {
        String uuid = headers.get("Unique-ID");
        String recordPath = headers.get("Record-File-Path");
        
        log.info("开始录音: uuid={}, path={}", uuid, recordPath);
        
        Call call = callService.getCallByUuid(uuid);
        if (call != null) {
            call.setRecordingPath(recordPath);
            callService.updateCall(call);
        }
    }

    /**
     * 处理录音停止事件
     */
    private void handleRecordStop(Map<String, String> headers) {
        String uuid = headers.get("Unique-ID");
        String recordPath = headers.get("Record-File-Path");
        
        log.info("停止录音: uuid={}, path={}", uuid, recordPath);
    }

    /**
     * 处理自定义事件
     */
    private void handleCustomEvent(Map<String, String> headers) {
        String subclass = headers.get("Event-Subclass");
        log.debug("自定义事件: subclass={}", subclass);
        
        // 处理特定的自定义事件
        if ("callcenter::info".equals(subclass)) {
            // 处理呼叫中心相关事件
        }
    }
}
```

#### 3.2.5 呼叫服务

```java
package com.callcenter.service;

import com.callcenter.esl.EslConnectionManager;
import com.callcenter.model.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * 呼叫服务
 * 提供呼叫控制相关功能
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class CallService {

    private final EslConnectionManager eslManager;
    private final RedisTemplate<String, Object> redisTemplate;
    
    private static final String CALL_KEY_PREFIX = "call:";
    private static final int CALL_EXPIRE_HOURS = 24;

    /**
     * 发起外呼
     * @param agentId 坐席ID
     * @param callerNumber 主叫号码（外显号码）
     * @param calleeNumber 被叫号码
     * @return 呼叫UUID
     */
    public String originate(String agentId, String callerNumber, String calleeNumber) {
        log.info("发起外呼: agent={}, caller={}, callee={}", 
                agentId, callerNumber, calleeNumber);
        
        // 获取坐席分机号
        Agent agent = getAgent(agentId);
        if (agent == null || agent.getState() != AgentState.READY) {
            throw new RuntimeException("坐席不可用");
        }
        
        // 构建originate命令
        // originate {变量}呼叫字符串 &应用(参数)
        StringBuilder cmd = new StringBuilder();
        cmd.append("{");
        cmd.append("origination_caller_id_number=").append(callerNumber).append(",");
        cmd.append("origination_caller_id_name=公司名称").append(",");
        cmd.append("agent_id=").append(agentId).append(",");
        cmd.append("direction=outbound").append(",");
        cmd.append("RECORD_STEREO=true");
        cmd.append("}");
        
        // A-leg: 先呼叫坐席分机
        cmd.append("user/").append(agent.getExtension());
        
        // B-leg: 坐席接听后呼叫客户
        cmd.append(" &bridge({origination_caller_id_number=")
           .append(callerNumber)
           .append("}sofia/gateway/carrier_primary/")
           .append(calleeNumber)
           .append(")");
        
        String result = eslManager.sendSyncApiCommand("originate", cmd.toString());
        
        if (result.startsWith("+OK")) {
            String uuid = result.substring(4).trim();
            log.info("外呼成功: uuid={}", uuid);
            return uuid;
        } else {
            log.error("外呼失败: {}", result);
            throw new RuntimeException("外呼失败: " + result);
        }
    }

    /**
     * 接听来电
     */
    public void answer(String uuid) {
        log.info("接听来电: uuid={}", uuid);
        eslManager.sendSyncApiCommand("uuid_answer", uuid);
    }

    /**
     * 挂断通话
     */
    public void hangup(String uuid) {
        log.info("挂断通话: uuid={}", uuid);
        eslManager.sendSyncApiCommand("uuid_kill", uuid);
    }

    /**
     * 挂断通话（带原因）
     */
    public void hangup(String uuid, String cause) {
        log.info("挂断通话: uuid={}, cause={}", uuid, cause);
        eslManager.sendSyncApiCommand("uuid_kill", uuid + " " + cause);
    }

    /**
     * 保持通话
     */
    public void hold(String uuid) {
        log.info("保持通话: uuid={}", uuid);
        eslManager.sendSyncApiCommand("uuid_hold", uuid);
    }

    /**
     * 取回保持
     */
    public void unhold(String uuid) {
        log.info("取回通话: uuid={}", uuid);
        eslManager.sendSyncApiCommand("uuid_hold", "off " + uuid);
    }

    /**
     * 静音
     */
    public void mute(String uuid) {
        log.info("静音: uuid={}", uuid);
        eslManager.sendSyncApiCommand("uuid_audio", uuid + " start mute");
    }

    /**
     * 取消静音
     */
    public void unmute(String uuid) {
        log.info("取消静音: uuid={}", uuid);
        eslManager.sendSyncApiCommand("uuid_audio", uuid + " stop");
    }

    /**
     * 盲转（直接转接）
     */
    public void blindTransfer(String uuid, String destination) {
        log.info("盲转: uuid={}, dest={}", uuid, destination);
        eslManager.sendSyncApiCommand("uuid_transfer", uuid + " " + destination);
    }

    /**
     * 咨询转（先咨询再转接）
     */
    public String consultTransfer(String uuid, String destination) {
        log.info("咨询转: uuid={}, dest={}", uuid, destination);
        
        // 先保持当前通话
        hold(uuid);
        
        // 发起咨询呼叫
        String consultCmd = "{origination_uuid=consult_" + uuid + "}user/" + destination;
        String result = eslManager.sendSyncApiCommand("originate", 
            consultCmd + " &park()");
        
        if (result.startsWith("+OK")) {
            return result.substring(4).trim();
        }
        throw new RuntimeException("咨询转失败");
    }

    /**
     * 完成咨询转接
     */
    public void completeTransfer(String originalUuid, String consultUuid) {
        log.info("完成咨询转: original={}, consult={}", originalUuid, consultUuid);
        eslManager.sendSyncApiCommand("uuid_bridge", originalUuid + " " + consultUuid);
    }

    /**
     * 三方通话
     */
    public void conference(String uuid1, String uuid2, String conferenceName) {
        log.info("三方通话: uuid1={}, uuid2={}, conf={}", uuid1, uuid2, conferenceName);
        
        // 将两个通道加入会议
        eslManager.sendSyncApiCommand("uuid_transfer", 
            uuid1 + " conference:" + conferenceName + " inline");
        eslManager.sendSyncApiCommand("uuid_transfer", 
            uuid2 + " conference:" + conferenceName + " inline");
    }

    /**
     * 播放语音
     */
    public void playback(String uuid, String filePath) {
        log.info("播放语音: uuid={}, file={}", uuid, filePath);
        eslManager.sendSyncApiCommand("uuid_broadcast", 
            uuid + " " + filePath + " both");
    }

    /**
     * 停止播放
     */
    public void stopPlayback(String uuid) {
        log.info("停止播放: uuid={}", uuid);
        eslManager.sendSyncApiCommand("uuid_break", uuid);
    }

    /**
     * 开始录音
     */
    public void startRecording(String uuid, String filePath) {
        log.info("开始录音: uuid={}, file={}", uuid, filePath);
        eslManager.sendSyncApiCommand("uuid_record", uuid + " start " + filePath);
    }

    /**
     * 停止录音
     */
    public void stopRecording(String uuid, String filePath) {
        log.info("停止录音: uuid={}", uuid);
        eslManager.sendSyncApiCommand("uuid_record", uuid + " stop " + filePath);
    }

    /**
     * 桥接两个通道
     */
    public void bridge(String uuid1, String uuid2) {
        log.info("桥接通道: uuid1={}, uuid2={}", uuid1, uuid2);
        eslManager.sendSyncApiCommand("uuid_bridge", uuid1 + " " + uuid2);
    }

    /**
     * 设置通道变量
     */
    public void setVariable(String uuid, String name, String value) {
        eslManager.sendSyncApiCommand("uuid_setvar", 
            uuid + " " + name + " " + value);
    }

    /**
     * 获取通道变量
     */
    public String getVariable(String uuid, String name) {
        return eslManager.sendSyncApiCommand("uuid_getvar", uuid + " " + name);
    }

    // ============ 呼叫记录管理 ============

    /**
     * 保存呼叫记录
     */
    public void saveCall(Call call) {
        String key = CALL_KEY_PREFIX + call.getUuid();
        redisTemplate.opsForValue().set(key, call, CALL_EXPIRE_HOURS, TimeUnit.HOURS);
    }

    /**
     * 获取呼叫记录
     */
    public Call getCallByUuid(String uuid) {
        String key = CALL_KEY_PREFIX + uuid;
        return (Call) redisTemplate.opsForValue().get(key);
    }

    /**
     * 更新呼叫记录
     */
    public void updateCall(Call call) {
        saveCall(call);
    }

    /**
     * 获取坐席信息
     */
    private Agent getAgent(String agentId) {
        // TODO: 从坐席服务获取
        return null;
    }
}
```

#### 3.2.6 ACD话务分配服务

```java
package com.callcenter.service;

import com.callcenter.esl.EslConnectionManager;
import com.callcenter.model.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * ACD话务分配服务
 * 实现多种分配策略：历史服务优先、最长空闲优先、轮询
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AcdService {

    private final EslConnectionManager eslManager;
    private final RedisTemplate<String, Object> redisTemplate;
    private final AgentService agentService;
    private final CallService callService;

    // 队列Key前缀
    private static final String QUEUE_KEY_PREFIX = "queue:";
    // 坐席最后服务客户映射
    private static final String AGENT_CUSTOMER_MAP_KEY = "agent:customer:map";
    // 坐席最后空闲时间
    private static final String AGENT_IDLE_TIME_KEY = "agent:idle:time:";
    // 轮询索引
    private final Map<String, Integer> roundRobinIndex = new ConcurrentHashMap<>();

    /**
     * 呼叫入队
     */
    public void enqueue(Call call) {
        String skillGroup = call.getSkillGroup();
        String queueKey = QUEUE_KEY_PREFIX + skillGroup;
        
        QueueItem item = new QueueItem();
        item.setCallUuid(call.getUuid());
        item.setCallerNumber(call.getCallerNumber());
        item.setSkillGroup(skillGroup);
        item.setEnqueueTime(LocalDateTime.now());
        item.setPriority(calculatePriority(call));
        
        // 使用SortedSet，按优先级和入队时间排序
        double score = item.getPriority() * 1000000000L + 
            System.currentTimeMillis() / 1000;
        redisTemplate.opsForZSet().add(queueKey, item, score);
        
        log.info("呼叫入队: uuid={}, queue={}, priority={}", 
                call.getUuid(), skillGroup, item.getPriority());
        
        // 尝试立即分配
        tryAssign(skillGroup);
    }

    /**
     * 计算优先级（数字越小优先级越高）
     */
    private int calculatePriority(Call call) {
        // VIP客户优先级高
        if ("VIP".equals(call.getCustomerLevel())) {
            return 1;
        }
        // 投诉优先
        if ("complaint".equals(call.getBusinessType())) {
            return 2;
        }
        // 普通客户
        return 5;
    }

    /**
     * 尝试分配队列中的呼叫
     */
    public void tryAssign(String skillGroup) {
        String queueKey = QUEUE_KEY_PREFIX + skillGroup;
        
        // 获取可用坐席
        List<Agent> availableAgents = agentService.getAvailableAgents(skillGroup);
        if (availableAgents.isEmpty()) {
            log.debug("技能组{}无可用坐席", skillGroup);
            return;
        }
        
        // 获取队列中的呼叫
        Set<Object> queueItems = redisTemplate.opsForZSet().range(queueKey, 0, 0);
        if (queueItems == null || queueItems.isEmpty()) {
            log.debug("技能组{}队列为空", skillGroup);
            return;
        }
        
        QueueItem item = (QueueItem) queueItems.iterator().next();
        Call call = callService.getCallByUuid(item.getCallUuid());
        
        if (call == null || call.getState() == CallState.HANGUP) {
            // 呼叫已结束，移除队列
            redisTemplate.opsForZSet().remove(queueKey, item);
            tryAssign(skillGroup);  // 继续处理下一个
            return;
        }
        
        // 选择坐席
        Agent selectedAgent = selectAgent(availableAgents, call);
        
        if (selectedAgent != null) {
            // 从队列移除
            redisTemplate.opsForZSet().remove(queueKey, item);
            
            // 分配呼叫
            assignCallToAgent(call, selectedAgent);
        }
    }

    /**
     * 选择坐席（多策略）
     */
    private Agent selectAgent(List<Agent> agents, Call call) {
        if (agents.isEmpty()) {
            return null;
        }
        
        // 策略1: 历史服务优先
        Agent lastAgent = findLastServingAgent(call.getCallerNumber(), agents);
        if (lastAgent != null) {
            log.info("历史服务优先: 客户{}分配给上次服务坐席{}", 
                    call.getCallerNumber(), lastAgent.getId());
            return lastAgent;
        }
        
        // 策略2: 最长空闲优先
        Agent longestIdleAgent = findLongestIdleAgent(agents);
        if (longestIdleAgent != null) {
            log.info("最长空闲优先: 选择坐席{}", longestIdleAgent.getId());
            return longestIdleAgent;
        }
        
        // 策略3: 轮询
        Agent roundRobinAgent = selectByRoundRobin(agents, call.getSkillGroup());
        log.info("轮询分配: 选择坐席{}", roundRobinAgent.getId());
        return roundRobinAgent;
    }

    /**
     * 查找上次服务该客户的坐席
     */
    private Agent findLastServingAgent(String customerNumber, List<Agent> agents) {
        String lastAgentId = (String) redisTemplate.opsForHash()
            .get(AGENT_CUSTOMER_MAP_KEY, customerNumber);
        
        if (lastAgentId != null) {
            return agents.stream()
                .filter(a -> a.getId().equals(lastAgentId))
                .findFirst()
                .orElse(null);
        }
        return null;
    }

    /**
     * 查找空闲时间最长的坐席
     */
    private Agent findLongestIdleAgent(List<Agent> agents) {
        return agents.stream()
            .max(Comparator.comparing(agent -> {
                String key = AGENT_IDLE_TIME_KEY + agent.getId();
                Long idleTime = (Long) redisTemplate.opsForValue().get(key);
                return idleTime != null ? idleTime : 0L;
            }))
            .orElse(null);
    }

    /**
     * 轮询选择坐席
     */
    private Agent selectByRoundRobin(List<Agent> agents, String skillGroup) {
        int currentIndex = roundRobinIndex.getOrDefault(skillGroup, 0);
        int nextIndex = (currentIndex + 1) % agents.size();
        roundRobinIndex.put(skillGroup, nextIndex);
        return agents.get(currentIndex);
    }

    /**
     * 分配呼叫给坐席
     */
    private void assignCallToAgent(Call call, Agent agent) {
        log.info("分配呼叫: uuid={} -> agent={}", call.getUuid(), agent.getId());
        
        // 更新坐席状态
        agentService.updateAgentState(agent.getId(), AgentState.RINGING);
        
        // 更新呼叫信息
        call.setAgentId(agent.getId());
        call.setAgentExtension(agent.getExtension());
        callService.updateCall(call);
        
        // 将呼叫转接到坐席分机
        // 方式1: 使用uuid_transfer
        eslManager.sendSyncApiCommand("uuid_transfer", 
            call.getUuid() + " " + agent.getExtension() + " XML default");
        
        // 方式2: 使用originate + bridge（更灵活）
        // String bridgeCmd = String.format(
        //     "{origination_uuid=%s,agent_id=%s}user/%s", 
        //     UUID.randomUUID(), agent.getId(), agent.getExtension());
        // eslManager.sendSyncApiCommand("uuid_broadcast", 
        //     call.getUuid() + " bridge::" + bridgeCmd + " both");
        
        // 记录坐席-客户关系（用于历史服务优先）
        redisTemplate.opsForHash().put(AGENT_CUSTOMER_MAP_KEY, 
            call.getCallerNumber(), agent.getId());
    }

    /**
     * 通话结束处理
     */
    public void onCallEnd(String agentId, Call call) {
        log.info("通话结束: agent={}, call={}", agentId, call.getUuid());
        
        // 更新坐席状态为后处理
        agentService.updateAgentState(agentId, AgentState.WRAP_UP);
        
        // 设置后处理超时（30秒后自动置闲）
        // 由定时任务处理
    }

    /**
     * 坐席置闲时记录空闲时间
     */
    public void onAgentReady(String agentId, String skillGroup) {
        String key = AGENT_IDLE_TIME_KEY + agentId;
        redisTemplate.opsForValue().set(key, System.currentTimeMillis());
        
        // 尝试分配队列中的呼叫
        tryAssign(skillGroup);
    }

    /**
     * 定时处理队列
     */
    @Scheduled(fixedDelay = 1000)
    public void processQueues() {
        // 获取所有技能组
        Set<String> skillGroups = agentService.getAllSkillGroups();
        for (String skillGroup : skillGroups) {
            tryAssign(skillGroup);
        }
    }

    /**
     * 获取队列状态
     */
    public QueueStatus getQueueStatus(String skillGroup) {
        String queueKey = QUEUE_KEY_PREFIX + skillGroup;
        
        QueueStatus status = new QueueStatus();
        status.setSkillGroup(skillGroup);
        status.setWaitingCount(redisTemplate.opsForZSet().size(queueKey));
        status.setAvailableAgents(agentService.getAvailableAgents(skillGroup).size());
        
        // 计算最长等待时间
        Set<Object> items = redisTemplate.opsForZSet().range(queueKey, 0, 0);
        if (items != null && !items.isEmpty()) {
            QueueItem firstItem = (QueueItem) items.iterator().next();
            long waitSeconds = java.time.Duration.between(
                firstItem.getEnqueueTime(), LocalDateTime.now()).getSeconds();
            status.setLongestWaitTime(waitSeconds);
        }
        
        return status;
    }

    /**
     * 溢出处理
     * 当技能组无可用坐席且等待时间超过阈值时，溢出到其他技能组
     */
    @Scheduled(fixedDelay = 5000)
    public void handleOverflow() {
        // 获取溢出配置
        Map<String, String> overflowConfig = getOverflowConfig();
        
        for (Map.Entry<String, String> entry : overflowConfig.entrySet()) {
            String sourceGroup = entry.getKey();
            String targetGroup = entry.getValue();
            
            String queueKey = QUEUE_KEY_PREFIX + sourceGroup;
            Set<Object> items = redisTemplate.opsForZSet().range(queueKey, 0, -1);
            
            if (items == null || items.isEmpty()) {
                continue;
            }
            
            // 检查是否需要溢出
            List<Agent> sourceAgents = agentService.getAvailableAgents(sourceGroup);
            List<Agent> targetAgents = agentService.getAvailableAgents(targetGroup);
            
            if (sourceAgents.isEmpty() && !targetAgents.isEmpty()) {
                for (Object obj : items) {
                    QueueItem item = (QueueItem) obj;
                    long waitSeconds = java.time.Duration.between(
                        item.getEnqueueTime(), LocalDateTime.now()).getSeconds();
                    
                    // 等待超过60秒，溢出
                    if (waitSeconds > 60) {
                        log.info("溢出处理: uuid={}, {}->{}",
                                item.getCallUuid(), sourceGroup, targetGroup);
                        
                        // 移出原队列
                        redisTemplate.opsForZSet().remove(queueKey, item);
                        
                        // 加入目标队列
                        item.setSkillGroup(targetGroup);
                        String targetQueueKey = QUEUE_KEY_PREFIX + targetGroup;
                        double score = item.getPriority() * 1000000000L + 
                            System.currentTimeMillis() / 1000;
                        redisTemplate.opsForZSet().add(targetQueueKey, item, score);
                        
                        // 尝试分配
                        tryAssign(targetGroup);
                    }
                }
            }
        }
    }

    private Map<String, String> getOverflowConfig() {
        // 配置：源技能组 -> 目标技能组
        Map<String, String> config = new HashMap<>();
        config.put("rescue_group", "default_group");
        config.put("housekeeping_group", "default_group");
        config.put("driver_group", "default_group");
        return config;
    }
}
```

#### 3.2.7 坐席服务

```java
package com.callcenter.service;

import com.callcenter.esl.EslConnectionManager;
import com.callcenter.model.*;
import com.callcenter.websocket.EventPushService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 坐席服务
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AgentService {

    private final RedisTemplate<String, Object> redisTemplate;
    private final EslConnectionManager eslManager;
    private final EventPushService eventPushService;
    private final AcdService acdService;

    private static final String AGENT_KEY_PREFIX = "agent:";
    private static final String AGENT_SET_KEY = "agents:all";
    private static final String SKILL_GROUP_AGENTS_KEY = "skillgroup:agents:";

    /**
     * 坐席签入
     */
    public void signIn(String agentId, String extension, List<String> skillGroups) {
        log.info("坐席签入: agentId={}, extension={}, skills={}", 
                agentId, extension, skillGroups);

        // 检查分机是否注册
        String regStatus = eslManager.sendSyncApiCommand("sofia_contact", 
            "internal/" + extension + "@${domain}");
        if (regStatus.contains("error")) {
            throw new RuntimeException("分机未注册: " + extension);
        }

        Agent agent = new Agent();
        agent.setId(agentId);
        agent.setExtension(extension);
        agent.setSkillGroups(skillGroups);
        agent.setState(AgentState.NOT_READY);
        agent.setSignInTime(LocalDateTime.now());
        
        // 保存坐席信息
        String key = AGENT_KEY_PREFIX + agentId;
        redisTemplate.opsForValue().set(key, agent);
        
        // 加入坐席集合
        redisTemplate.opsForSet().add(AGENT_SET_KEY, agentId);
        
        // 加入技能组
        for (String skillGroup : skillGroups) {
            redisTemplate.opsForSet().add(SKILL_GROUP_AGENTS_KEY + skillGroup, agentId);
        }

        // 推送状态变更
        eventPushService.pushAgentState(agent);
    }

    /**
     * 坐席签出
     */
    public void signOut(String agentId) {
        log.info("坐席签出: agentId={}", agentId);

        Agent agent = getAgent(agentId);
        if (agent == null) {
            return;
        }

        // 检查是否在通话中
        if (agent.getState() == AgentState.BUSY || 
            agent.getState() == AgentState.RINGING) {
            throw new RuntimeException("坐席正在通话中，无法签出");
        }

        // 从技能组移除
        for (String skillGroup : agent.getSkillGroups()) {
            redisTemplate.opsForSet().remove(SKILL_GROUP_AGENTS_KEY + skillGroup, agentId);
        }

        // 从坐席集合移除
        redisTemplate.opsForSet().remove(AGENT_SET_KEY, agentId);

        // 删除坐席信息
        String key = AGENT_KEY_PREFIX + agentId;
        redisTemplate.delete(key);

        // 推送签出事件
        agent.setState(AgentState.OFFLINE);
        eventPushService.pushAgentState(agent);
    }

    /**
     * 坐席置闲
     */
    public void ready(String agentId) {
        log.info("坐席置闲: agentId={}", agentId);
        
        Agent agent = getAgent(agentId);
        if (agent == null) {
            throw new RuntimeException("坐席不存在");
        }
        
        if (agent.getState() == AgentState.BUSY) {
            throw new RuntimeException("坐席正在通话中");
        }
        
        updateAgentState(agentId, AgentState.READY);
        
        // 通知ACD有坐席可用
        for (String skillGroup : agent.getSkillGroups()) {
            acdService.onAgentReady(agentId, skillGroup);
        }
    }

    /**
     * 坐席置忙
     */
    public void notReady(String agentId, String reason) {
        log.info("坐席置忙: agentId={}, reason={}", agentId, reason);
        
        Agent agent = getAgent(agentId);
        if (agent == null) {
            throw new RuntimeException("坐席不存在");
        }
        
        if (agent.getState() == AgentState.BUSY) {
            throw new RuntimeException("坐席正在通话中");
        }
        
        agent.setNotReadyReason(reason);
        updateAgentState(agentId, AgentState.NOT_READY);
    }

    /**
     * 坐席小休
     */
    public void pause(String agentId, String reason) {
        log.info("坐席小休: agentId={}, reason={}", agentId, reason);
        
        Agent agent = getAgent(agentId);
        if (agent == null) {
            throw new RuntimeException("坐席不存在");
        }
        
        if (agent.getState() == AgentState.BUSY) {
            throw new RuntimeException("坐席正在通话中");
        }
        
        agent.setPauseReason(reason);
        updateAgentState(agentId, AgentState.PAUSED);
    }

    /**
     * 更新坐席状态
     */
    public void updateAgentState(String agentId, AgentState newState) {
        Agent agent = getAgent(agentId);
        if (agent == null) {
            return;
        }
        
        AgentState oldState = agent.getState();
        agent.setState(newState);
        agent.setStateTime(LocalDateTime.now());
        
        // 保存
        String key = AGENT_KEY_PREFIX + agentId;
        redisTemplate.opsForValue().set(key, agent);
        
        log.info("坐席状态变更: agent={}, {} -> {}", agentId, oldState, newState);
        
        // 推送状态变更
        eventPushService.pushAgentState(agent);
    }

    /**
     * 获取坐席信息
     */
    public Agent getAgent(String agentId) {
        String key = AGENT_KEY_PREFIX + agentId;
        return (Agent) redisTemplate.opsForValue().get(key);
    }

    /**
     * 获取技能组可用坐席
     */
    public List<Agent> getAvailableAgents(String skillGroup) {
        Set<Object> agentIds = redisTemplate.opsForSet()
            .members(SKILL_GROUP_AGENTS_KEY + skillGroup);
        
        if (agentIds == null || agentIds.isEmpty()) {
            return Collections.emptyList();
        }
        
        return agentIds.stream()
            .map(id -> getAgent((String) id))
            .filter(agent -> agent != null && agent.getState() == AgentState.READY)
            .collect(Collectors.toList());
    }

    /**
     * 获取所有技能组
     */
    public Set<String> getAllSkillGroups() {
        Set<String> keys = redisTemplate.keys(SKILL_GROUP_AGENTS_KEY + "*");
        if (keys == null) {
            return Collections.emptySet();
        }
        return keys.stream()
            .map(key -> key.replace(SKILL_GROUP_AGENTS_KEY, ""))
            .collect(Collectors.toSet());
    }

    /**
     * 获取所有坐席状态（监控大屏）
     */
    public List<Agent> getAllAgents() {
        Set<Object> agentIds = redisTemplate.opsForSet().members(AGENT_SET_KEY);
        if (agentIds == null) {
            return Collections.emptyList();
        }
        return agentIds.stream()
            .map(id -> getAgent((String) id))
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
    }

    /**
     * 获取坐席状态统计
     */
    public Map<AgentState, Long> getAgentStateStats() {
        return getAllAgents().stream()
            .collect(Collectors.groupingBy(Agent::getState, Collectors.counting()));
    }
}
```

### 3.3 Go ESL开发

如果您的团队更熟悉Go语言，以下是Go版本的ESL开发示例：

#### 3.3.1 项目结构

```
callcenter-cti-go/
├── go.mod
├── go.sum
├── main.go
├── config/
│   └── config.go
├── esl/
│   ├── client.go           # ESL客户端
│   ├── event.go            # 事件定义
│   └── handler.go          # 事件处理器
├── service/
│   ├── agent.go            # 坐席服务
│   ├── call.go             # 呼叫服务
│   └── acd.go              # ACD服务
├── model/
│   └── models.go           # 数据模型
├── api/
│   ├── router.go           # 路由
│   ├── agent_handler.go    # 坐席API
│   └── call_handler.go     # 呼叫API
└── websocket/
    └── hub.go              # WebSocket推送
```

#### 3.3.2 ESL客户端

```go
// esl/client.go
package esl

import (
    "bufio"
    "fmt"
    "log"
    "net"
    "net/textproto"
    "strconv"
    "strings"
    "sync"
    "time"
)

// ESL客户端
type Client struct {
    conn       net.Conn
    reader     *textproto.Reader
    writer     *bufio.Writer
    mu         sync.Mutex
    connected  bool
    password   string
    eventChan  chan *Event
    handlers   []EventHandler
    reconnect  bool
}

// 事件处理器接口
type EventHandler interface {
    Handle(event *Event)
}

// 创建ESL客户端
func NewClient(host string, port int, password string) (*Client, error) {
    client := &Client{
        password:  password,
        eventChan: make(chan *Event, 1000),
        handlers:  make([]EventHandler, 0),
        reconnect: true,
    }
    
    err := client.connect(host, port)
    if err != nil {
        return nil, err
    }
    
    // 启动事件读取协程
    go client.readLoop()
    
    // 启动事件分发协程
    go client.dispatchLoop()
    
    return client, nil
}

// 连接FreeSWITCH
func (c *Client) connect(host string, port int) error {
    addr := fmt.Sprintf("%s:%d", host, port)
    
    conn, err := net.DialTimeout("tcp", addr, 10*time.Second)
    if err != nil {
        return fmt.Errorf("连接失败: %v", err)
    }
    
    c.conn = conn
    c.reader = textproto.NewReader(bufio.NewReader(conn))
    c.writer = bufio.NewWriter(conn)
    
    // 读取欢迎消息
    _, err = c.readMessage()
    if err != nil {
        return err
    }
    
    // 认证
    err = c.auth()
    if err != nil {
        return err
    }
    
    c.connected = true
    log.Println("ESL连接成功")
    
    return nil
}

// 认证
func (c *Client) auth() error {
    _, err := c.sendCommand(fmt.Sprintf("auth %s", c.password))
    if err != nil {
        return fmt.Errorf("认证失败: %v", err)
    }
    return nil
}

// 发送命令
func (c *Client) sendCommand(command string) (*Message, error) {
    c.mu.Lock()
    defer c.mu.Unlock()
    
    _, err := c.writer.WriteString(command + "\n\n")
    if err != nil {
        return nil, err
    }
    c.writer.Flush()
    
    return c.readMessage()
}

// 发送API命令
func (c *Client) API(command string, args string) (string, error) {
    cmd := fmt.Sprintf("api %s %s", command, args)
    msg, err := c.sendCommand(cmd)
    if err != nil {
        return "", err
    }
    return msg.Body, nil
}

// 发送后台API命令
func (c *Client) BgAPI(command string, args string) (string, error) {
    cmd := fmt.Sprintf("bgapi %s %s", command, args)
    msg, err := c.sendCommand(cmd)
    if err != nil {
        return "", err
    }
    return msg.GetHeader("Job-UUID"), nil
}

// 订阅事件
func (c *Client) Subscribe(events ...string) error {
    eventStr := strings.Join(events, " ")
    _, err := c.sendCommand(fmt.Sprintf("event plain %s", eventStr))
    return err
}

// 订阅所有事件
func (c *Client) SubscribeAll() error {
    _, err := c.sendCommand("event plain ALL")
    return err
}

// 读取消息
func (c *Client) readMessage() (*Message, error) {
    msg := &Message{
        Headers: make(map[string]string),
    }
    
    // 读取headers
    for {
        line, err := c.reader.ReadLine()
        if err != nil {
            return nil, err
        }
        
        if line == "" {
            break
        }
        
        parts := strings.SplitN(line, ": ", 2)
        if len(parts) == 2 {
            msg.Headers[parts[0]] = parts[1]
        }
    }
    
    // 读取body
    if lenStr := msg.GetHeader("Content-Length"); lenStr != "" {
        length, _ := strconv.Atoi(lenStr)
        if length > 0 {
            body := make([]byte, length)
            _, err := c.reader.R.Read(body)
            if err != nil {
                return nil, err
            }
            msg.Body = string(body)
        }
    }
    
    return msg, nil
}

// 事件读取循环
func (c *Client) readLoop() {
    for c.connected {
        msg, err := c.readMessage()
        if err != nil {
            log.Printf("读取消息错误: %v", err)
            c.connected = false
            if c.reconnect {
                c.tryReconnect()
            }
            return
        }
        
        contentType := msg.GetHeader("Content-Type")
        if contentType == "text/event-plain" {
            event := parseEvent(msg)
            c.eventChan <- event
        }
    }
}

// 事件分发循环
func (c *Client) dispatchLoop() {
    for event := range c.eventChan {
        for _, handler := range c.handlers {
            go handler.Handle(event)
        }
    }
}

// 尝试重连
func (c *Client) tryReconnect() {
    for i := 0; i < 10; i++ {
        time.Sleep(5 * time.Second)
        log.Printf("尝试重连ESL (第%d次)...", i+1)
        
        // TODO: 重新连接
    }
}

// 添加事件处理器
func (c *Client) AddHandler(handler EventHandler) {
    c.handlers = append(c.handlers, handler)
}

// 关闭连接
func (c *Client) Close() {
    c.connected = false
    c.reconnect = false
    if c.conn != nil {
        c.conn.Close()
    }
    close(c.eventChan)
}

// 消息结构
type Message struct {
    Headers map[string]string
    Body    string
}

func (m *Message) GetHeader(name string) string {
    if v, ok := m.Headers[name]; ok {
        return v
    }
    return ""
}

// 解析事件
func parseEvent(msg *Message) *Event {
    event := &Event{
        Headers: make(map[string]string),
    }
    
    // 解析body中的headers
    lines := strings.Split(msg.Body, "\n")
    for _, line := range lines {
        parts := strings.SplitN(line, ": ", 2)
        if len(parts) == 2 {
            // URL解码
            value := strings.ReplaceAll(parts[1], "%20", " ")
            event.Headers[parts[0]] = value
        }
    }
    
    event.Name = event.GetHeader("Event-Name")
    event.UUID = event.GetHeader("Unique-ID")
    
    return event
}
```

#### 3.3.3 事件定义

```go
// esl/event.go
package esl

// 事件结构
type Event struct {
    Name    string
    UUID    string
    Headers map[string]string
}

func (e *Event) GetHeader(name string) string {
    if v, ok := e.Headers[name]; ok {
        return v
    }
    return ""
}

// 事件名称常量
const (
    EventChannelCreate         = "CHANNEL_CREATE"
    EventChannelAnswer         = "CHANNEL_ANSWER"
    EventChannelBridge         = "CHANNEL_BRIDGE"
    EventChannelUnbridge       = "CHANNEL_UNBRIDGE"
    EventChannelHangup         = "CHANNEL_HANGUP"
    EventChannelHangupComplete = "CHANNEL_HANGUP_COMPLETE"
    EventChannelState          = "CHANNEL_STATE"
    EventDTMF                  = "DTMF"
    EventRecordStart           = "RECORD_START"
    EventRecordStop            = "RECORD_STOP"
    EventCustom                = "CUSTOM"
)
```

#### 3.3.4 事件处理器

```go
// esl/handler.go
package esl

import (
    "callcenter/service"
    "log"
)

// 呼叫中心事件处理器
type CallCenterHandler struct {
    callService  *service.CallService
    agentService *service.AgentService
    acdService   *service.AcdService
}

func NewCallCenterHandler(
    callService *service.CallService,
    agentService *service.AgentService,
    acdService *service.AcdService,
) *CallCenterHandler {
    return &CallCenterHandler{
        callService:  callService,
        agentService: agentService,
        acdService:   acdService,
    }
}

func (h *CallCenterHandler) Handle(event *Event) {
    switch event.Name {
    case EventChannelCreate:
        h.handleChannelCreate(event)
    case EventChannelAnswer:
        h.handleChannelAnswer(event)
    case EventChannelBridge:
        h.handleChannelBridge(event)
    case EventChannelHangup:
        h.handleChannelHangup(event)
    case EventChannelHangupComplete:
        h.handleChannelHangupComplete(event)
    case EventDTMF:
        h.handleDTMF(event)
    }
}

func (h *CallCenterHandler) handleChannelCreate(event *Event) {
    uuid := event.UUID
    direction := event.GetHeader("Call-Direction")
    callerNumber := event.GetHeader("Caller-Caller-ID-Number")
    calleeNumber := event.GetHeader("Caller-Destination-Number")
    
    log.Printf("新呼叫: uuid=%s, direction=%s, caller=%s, callee=%s",
        uuid, direction, callerNumber, calleeNumber)
    
    // 创建呼叫记录
    call := &service.Call{
        UUID:         uuid,
        Direction:    direction,
        CallerNumber: callerNumber,
        CalleeNumber: calleeNumber,
        State:        "ringing",
    }
    
    h.callService.SaveCall(call)
    
    // 呼入排队
    if direction == "inbound" {
        skillGroup := event.GetHeader("variable_skill_group")
        call.SkillGroup = skillGroup
        h.acdService.Enqueue(call)
    }
}

func (h *CallCenterHandler) handleChannelAnswer(event *Event) {
    uuid := event.UUID
    log.Printf("通话接听: uuid=%s", uuid)
    
    call := h.callService.GetCall(uuid)
    if call != nil {
        call.State = "answered"
        h.callService.UpdateCall(call)
    }
}

func (h *CallCenterHandler) handleChannelBridge(event *Event) {
    uuid := event.UUID
    otherUUID := event.GetHeader("Other-Leg-Unique-ID")
    log.Printf("通道桥接: uuid=%s, other=%s", uuid, otherUUID)
    
    call := h.callService.GetCall(uuid)
    if call != nil {
        call.State = "bridged"
        call.BridgeUUID = otherUUID
        h.callService.UpdateCall(call)
    }
}

func (h *CallCenterHandler) handleChannelHangup(event *Event) {
    uuid := event.UUID
    hangupCause := event.GetHeader("Hangup-Cause")
    log.Printf("通话挂断: uuid=%s, cause=%s", uuid, hangupCause)
    
    call := h.callService.GetCall(uuid)
    if call != nil {
        call.State = "hangup"
        call.HangupCause = hangupCause
        h.callService.UpdateCall(call)
        
        // 更新坐席状态
        if call.AgentID != "" {
            h.acdService.OnCallEnd(call.AgentID, call)
        }
    }
}

func (h *CallCenterHandler) handleChannelHangupComplete(event *Event) {
    uuid := event.UUID
    billsec := event.GetHeader("variable_billsec")
    recordingPath := event.GetHeader("variable_recording_path")
    
    log.Printf("通话结束: uuid=%s, billsec=%s, recording=%s",
        uuid, billsec, recordingPath)
    
    call := h.callService.GetCall(uuid)
    if call != nil {
        call.Billsec = billsec
        call.RecordingPath = recordingPath
        h.callService.UpdateCall(call)
    }
}

func (h *CallCenterHandler) handleDTMF(event *Event) {
    uuid := event.UUID
    digit := event.GetHeader("DTMF-Digit")
    log.Printf("DTMF按键: uuid=%s, digit=%s", uuid, digit)
}
```

---

## 第四部分 呼叫中心场景开发实践

### 4.1 预测式外呼实现

```java
package com.callcenter.service;

import com.callcenter.esl.EslConnectionManager;
import com.callcenter.model.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 预测式外呼服务
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class PredictiveDialerService {

    private final EslConnectionManager eslManager;
    private final AgentService agentService;
    private final CallService callService;

    // 外呼任务
    private final Map<String, DialTask> activeTasks = new ConcurrentHashMap<>();
    // 外呼线程池
    private final ExecutorService dialerExecutor = Executors.newFixedThreadPool(10);

    /**
     * 创建外呼任务
     */
    public String createTask(DialTaskRequest request) {
        String taskId = UUID.randomUUID().toString();
        
        DialTask task = new DialTask();
        task.setId(taskId);
        task.setName(request.getName());
        task.setSkillGroup(request.getSkillGroup());
        task.setCallerIdNumber(request.getCallerIdNumber());
        task.setNumbers(new LinkedBlockingQueue<>(request.getNumbers()));
        task.setMaxConcurrent(request.getMaxConcurrent());
        task.setState(TaskState.CREATED);
        task.setStats(new DialStats());
        
        activeTasks.put(taskId, task);
        log.info("创建外呼任务: taskId={}, name={}, numbers={}", 
                taskId, request.getName(), request.getNumbers().size());
        
        return taskId;
    }

    /**
     * 启动任务
     */
    public void startTask(String taskId) {
        DialTask task = activeTasks.get(taskId);
        if (task == null) {
            throw new RuntimeException("任务不存在");
        }
        
        task.setState(TaskState.RUNNING);
        log.info("启动外呼任务: {}", taskId);
        
        // 启动外呼循环
        dialerExecutor.submit(() -> dialLoop(task));
    }

    /**
     * 暂停任务
     */
    public void pauseTask(String taskId) {
        DialTask task = activeTasks.get(taskId);
        if (task != null) {
            task.setState(TaskState.PAUSED);
            log.info("暂停外呼任务: {}", taskId);
        }
    }

    /**
     * 恢复任务
     */
    public void resumeTask(String taskId) {
        DialTask task = activeTasks.get(taskId);
        if (task != null && task.getState() == TaskState.PAUSED) {
            task.setState(TaskState.RUNNING);
            dialerExecutor.submit(() -> dialLoop(task));
            log.info("恢复外呼任务: {}", taskId);
        }
    }

    /**
     * 停止任务
     */
    public void stopTask(String taskId) {
        DialTask task = activeTasks.get(taskId);
        if (task != null) {
            task.setState(TaskState.STOPPED);
            log.info("停止外呼任务: {}", taskId);
        }
    }

    /**
     * 外呼循环
     */
    private void dialLoop(DialTask task) {
        log.info("开始外呼循环: taskId={}", task.getId());
        
        while (task.getState() == TaskState.RUNNING && !task.getNumbers().isEmpty()) {
            try {
                // 计算应该发起的呼叫数
                int dialCount = calculateDialCount(task);
                
                if (dialCount <= 0) {
                    // 没有空闲坐席，等待
                    Thread.sleep(1000);
                    continue;
                }
                
                // 发起外呼
                for (int i = 0; i < dialCount; i++) {
                    String number = task.getNumbers().poll();
                    if (number == null) {
                        break;
                    }
                    
                    dial(task, number);
                }
                
                // 短暂等待，控制拨打速率
                Thread.sleep(500);
                
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            } catch (Exception e) {
                log.error("外呼异常: {}", e.getMessage(), e);
            }
        }
        
        // 任务完成
        if (task.getNumbers().isEmpty()) {
            task.setState(TaskState.COMPLETED);
        }
        
        log.info("外呼循环结束: taskId={}, state={}", task.getId(), task.getState());
    }

    /**
     * 计算应该发起的呼叫数（预测算法）
     */
    private int calculateDialCount(DialTask task) {
        // 获取可用坐席数
        List<Agent> availableAgents = agentService.getAvailableAgents(task.getSkillGroup());
        int availableAgentCount = availableAgents.size();
        
        if (availableAgentCount == 0) {
            return 0;
        }
        
        // 当前正在拨打的数量
        int currentDialing = task.getStats().getCurrentDialing().get();
        
        // 历史接通率（动态计算）
        double connectRate = calculateConnectRate(task);
        
        // 预测式算法：
        // 目标：让坐席接到电话时，刚好有客户接通
        // 预测需要拨打数 = 空闲坐席数 / 接通率 * 系数
        double factor = 1.2; // 超拨系数，避免坐席空闲
        int predictedDial = (int) Math.ceil(availableAgentCount / connectRate * factor);
        
        // 限制最大并发
        int maxAllowed = task.getMaxConcurrent() - currentDialing;
        
        return Math.min(predictedDial, maxAllowed);
    }

    /**
     * 计算接通率
     */
    private double calculateConnectRate(DialTask task) {
        DialStats stats = task.getStats();
        int total = stats.getTotalDialed().get();
        int connected = stats.getConnected().get();
        
        if (total < 10) {
            // 样本太少，使用默认值
            return 0.3;
        }
        
        double rate = (double) connected / total;
        // 限制范围
        return Math.max(0.1, Math.min(0.8, rate));
    }

    /**
     * 发起单个外呼
     */
    private void dial(DialTask task, String number) {
        log.info("外呼: taskId={}, number={}", task.getId(), number);
        
        task.getStats().getTotalDialed().incrementAndGet();
        task.getStats().getCurrentDialing().incrementAndGet();
        
        try {
            // 构建originate命令
            // 先拨打客户，客户接听后转到队列等待坐席
            StringBuilder cmd = new StringBuilder();
            cmd.append("{");
            cmd.append("origination_caller_id_number=").append(task.getCallerIdNumber()).append(",");
            cmd.append("task_id=").append(task.getId()).append(",");
            cmd.append("dial_number=").append(number).append(",");
            cmd.append("dialer_mode=predictive");
            cmd.append("}");
            
            // 通过网关外呼
            cmd.append("sofia/gateway/carrier_primary/").append(number);
            
            // 客户接听后的处理
            cmd.append(" &lua(predictive_handler.lua)");
            
            String result = eslManager.sendSyncApiCommand("originate", cmd.toString());
            
            if (!result.startsWith("+OK")) {
                log.warn("外呼失败: number={}, result={}", number, result);
                task.getStats().getFailed().incrementAndGet();
            }
            
        } catch (Exception e) {
            log.error("外呼异常: number={}", number, e);
            task.getStats().getFailed().incrementAndGet();
        } finally {
            task.getStats().getCurrentDialing().decrementAndGet();
        }
    }

    /**
     * 客户接听回调（由事件触发）
     */
    public void onCustomerAnswer(String uuid, String taskId) {
        DialTask task = activeTasks.get(taskId);
        if (task == null) {
            eslManager.sendSyncApiCommand("uuid_kill", uuid + " NO_ROUTE_DESTINATION");
            return;
        }
        
        task.getStats().getConnected().incrementAndGet();
        
        // 查找可用坐席
        List<Agent> agents = agentService.getAvailableAgents(task.getSkillGroup());
        
        if (agents.isEmpty()) {
            // 无可用坐席，播放等待音乐
            eslManager.sendSyncApiCommand("uuid_broadcast", 
                uuid + " ivr/please_wait.wav both");
            
            // 放入等待队列
            // TODO: 实现等待逻辑
            
            // 超时处理
            task.getStats().getAbandoned().incrementAndGet();
        } else {
            // 转接坐席
            Agent agent = agents.get(0);
            agentService.updateAgentState(agent.getId(), AgentState.RINGING);
            
            eslManager.sendSyncApiCommand("uuid_transfer", 
                uuid + " " + agent.getExtension() + " XML default");
            
            task.getStats().getTransferred().incrementAndGet();
        }
    }

    /**
     * 获取任务统计
     */
    public DialStats getTaskStats(String taskId) {
        DialTask task = activeTasks.get(taskId);
        return task != null ? task.getStats() : null;
    }
}

/**
 * 外呼任务
 */
@Data
class DialTask {
    private String id;
    private String name;
    private String skillGroup;
    private String callerIdNumber;
    private BlockingQueue<String> numbers;
    private int maxConcurrent;
    private TaskState state;
    private DialStats stats;
}

/**
 * 任务状态
 */
enum TaskState {
    CREATED, RUNNING, PAUSED, STOPPED, COMPLETED
}

/**
 * 外呼统计
 */
@Data
class DialStats {
    private AtomicInteger totalDialed = new AtomicInteger(0);
    private AtomicInteger currentDialing = new AtomicInteger(0);
    private AtomicInteger connected = new AtomicInteger(0);
    private AtomicInteger transferred = new AtomicInteger(0);
    private AtomicInteger failed = new AtomicInteger(0);
    private AtomicInteger abandoned = new AtomicInteger(0);
}
```

### 4.2 机器人外呼实现

```java
package com.callcenter.service;

import com.callcenter.esl.EslConnectionManager;
import com.callcenter.model.*;
import com.callcenter.tts.TtsService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.*;

/**
 * 机器人外呼服务
 * 用于批量通知、回访、满意度调查
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class RobotDialerService {

    private final EslConnectionManager eslManager;
    private final TtsService ttsService;

    private final Map<String, RobotTask> activeTasks = new ConcurrentHashMap<>();
    private final ExecutorService executor = Executors.newFixedThreadPool(20);

    /**
     * 创建机器人外呼任务
     */
    public String createTask(RobotTaskRequest request) {
        String taskId = UUID.randomUUID().toString();
        
        RobotTask task = new RobotTask();
        task.setId(taskId);
        task.setType(request.getType()); // NOTIFICATION, SURVEY, CALLBACK
        task.setNumbers(new LinkedBlockingQueue<>(request.getNumbers()));
        task.setTtsText(request.getTtsText());
        task.setVariables(request.getVariables()); // 每个号码的变量
        task.setMaxConcurrent(request.getMaxConcurrent());
        task.setRetryTimes(request.getRetryTimes());
        task.setCollectDtmf(request.isCollectDtmf());
        task.setState(TaskState.CREATED);
        task.setResults(new ConcurrentHashMap<>());
        
        activeTasks.put(taskId, task);
        return taskId;
    }

    /**
     * 启动任务
     */
    public void startTask(String taskId) {
        RobotTask task = activeTasks.get(taskId);
        if (task == null) {
            throw new RuntimeException("任务不存在");
        }
        
        task.setState(TaskState.RUNNING);
        executor.submit(() -> robotDialLoop(task));
    }

    /**
     * 机器人外呼循环
     */
    private void robotDialLoop(RobotTask task) {
        log.info("开始机器人外呼: taskId={}", task.getId());
        
        Semaphore semaphore = new Semaphore(task.getMaxConcurrent());
        
        while (task.getState() == TaskState.RUNNING) {
            String number = task.getNumbers().poll();
            if (number == null) {
                break;
            }
            
            try {
                semaphore.acquire();
                executor.submit(() -> {
                    try {
                        robotCall(task, number);
                    } finally {
                        semaphore.release();
                    }
                });
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
        
        // 等待所有呼叫完成
        try {
            semaphore.acquire(task.getMaxConcurrent());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        task.setState(TaskState.COMPLETED);
        log.info("机器人外呼完成: taskId={}", task.getId());
    }

    /**
     * 单个机器人呼叫
     */
    private void robotCall(RobotTask task, String number) {
        log.info("机器人呼叫: taskId={}, number={}", task.getId(), number);
        
        RobotCallResult result = new RobotCallResult();
        result.setNumber(number);
        result.setStartTime(System.currentTimeMillis());
        
        try {
            // 替换TTS文本中的变量
            String ttsText = replaceTtsVariables(task.getTtsText(), 
                task.getVariables().get(number));
            
            // 生成语音文件
            String audioFile = ttsService.synthesize(ttsText);
            
            // 构建外呼命令
            StringBuilder cmd = new StringBuilder();
            cmd.append("{");
            cmd.append("task_id=").append(task.getId()).append(",");
            cmd.append("dial_number=").append(number).append(",");
            cmd.append("robot_mode=true").append(",");
            cmd.append("audio_file=").append(audioFile);
            cmd.append("}");
            
            cmd.append("sofia/gateway/carrier_primary/").append(number);
            
            if (task.isCollectDtmf()) {
                // 需要收集按键
                cmd.append(" &lua(robot_survey.lua)");
            } else {
                // 纯播报
                cmd.append(" &playback(").append(audioFile).append(")");
            }
            
            String apiResult = eslManager.sendSyncApiCommand("originate", cmd.toString());
            
            if (apiResult.startsWith("+OK")) {
                result.setStatus("SUCCESS");
                result.setCallUuid(apiResult.substring(4).trim());
            } else {
                result.setStatus("FAILED");
                result.setError(apiResult);
                
                // 重试逻辑
                handleRetry(task, number);
            }
            
        } catch (Exception e) {
            log.error("机器人呼叫异常: number={}", number, e);
            result.setStatus("ERROR");
            result.setError(e.getMessage());
            handleRetry(task, number);
        }
        
        result.setEndTime(System.currentTimeMillis());
        task.getResults().put(number, result);
    }

    /**
     * 替换TTS变量
     */
    private String replaceTtsVariables(String template, Map<String, String> vars) {
        if (vars == null || vars.isEmpty()) {
            return template;
        }
        
        String result = template;
        for (Map.Entry<String, String> entry : vars.entrySet()) {
            result = result.replace("${" + entry.getKey() + "}", entry.getValue());
        }
        return result;
    }

    /**
     * 处理重试
     */
    private void handleRetry(RobotTask task, String number) {
        int retryCount = task.getRetryCount().getOrDefault(number, 0);
        if (retryCount < task.getRetryTimes()) {
            task.getRetryCount().put(number, retryCount + 1);
            task.getNumbers().offer(number);
            log.info("加入重试队列: number={}, retry={}", number, retryCount + 1);
        }
    }

    /**
     * 处理DTMF按键结果（由事件触发）
     */
    public void onDtmfResult(String uuid, String taskId, String number, String digit) {
        RobotTask task = activeTasks.get(taskId);
        if (task == null) {
            return;
        }
        
        RobotCallResult result = task.getResults().get(number);
        if (result != null) {
            result.setDtmfDigit(digit);
            log.info("收到按键: taskId={}, number={}, digit={}", taskId, number, digit);
        }
    }

    /**
     * 获取任务结果
     */
    public List<RobotCallResult> getTaskResults(String taskId) {
        RobotTask task = activeTasks.get(taskId);
        if (task == null) {
            return Collections.emptyList();
        }
        return new ArrayList<>(task.getResults().values());
    }
}
```

### 4.3 班长监控实现

```java
package com.callcenter.service;

import com.callcenter.esl.EslConnectionManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * 班长监控服务
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SupervisorService {

    private final EslConnectionManager eslManager;

    /**
     * 监听坐席通话
     * 班长可以听到双方说话，但双方听不到班长
     */
    public void spy(String supervisorExtension, String targetUuid) {
        log.info("开始监听: supervisor={}, target={}", supervisorExtension, targetUuid);
        
        // 使用eavesdrop应用
        // originate user/supervisor &eavesdrop(target_uuid)
        String cmd = String.format(
            "{origination_caller_id_name=监听}user/%s &eavesdrop(%s)",
            supervisorExtension, targetUuid
        );
        
        String result = eslManager.sendSyncApiCommand("originate", cmd);
        
        if (!result.startsWith("+OK")) {
            throw new RuntimeException("监听失败: " + result);
        }
    }

    /**
     * 强插通话
     * 班长加入通话，三方都可以听到
     */
    public void barge(String supervisorExtension, String targetUuid) {
        log.info("强插通话: supervisor={}, target={}", supervisorExtension, targetUuid);
        
        // 使用三方会议实现
        String confName = "barge_" + targetUuid;
        
        // 1. 将目标通话转入会议
        eslManager.sendSyncApiCommand("uuid_transfer", 
            targetUuid + " conference:" + confName + "+flags{mute} inline");
        
        // 2. 呼叫班长加入会议
        String cmd = String.format(
            "{origination_caller_id_name=强插}user/%s &conference(%s)",
            supervisorExtension, confName
        );
        
        eslManager.sendSyncApiCommand("originate", cmd);
    }

    /**
     * 强拆通话
     * 强制挂断坐席通话
     */
    public void hangup(String targetUuid, String reason) {
        log.info("强拆通话: target={}, reason={}", targetUuid, reason);
        
        eslManager.sendSyncApiCommand("uuid_kill", targetUuid + " " + reason);
    }

    /**
     * 密语（耳语）
     * 班长对坐席说话，客户听不到
     */
    public void whisper(String supervisorExtension, String agentUuid) {
        log.info("密语: supervisor={}, agent={}", supervisorExtension, agentUuid);
        
        // 使用eavesdrop的whisper模式
        String cmd = String.format(
            "{origination_caller_id_name=密语}user/%s &eavesdrop(%s whisper)",
            supervisorExtension, agentUuid
        );
        
        eslManager.sendSyncApiCommand("originate", cmd);
    }

    /**
     * 获取实时通话列表
     */
    public String getActiveCalls() {
        return eslManager.sendSyncApiCommand("show", "calls");
    }

    /**
     * 获取坐席通道信息
     */
    public String getChannelInfo(String uuid) {
        return eslManager.sendSyncApiCommand("uuid_dump", uuid);
    }
}
```

---

## 第五部分 高可用部署方案

### 5.1 主备架构

```
                           ┌─────────────────┐
                           │   Keepalived    │
                           │   VIP漂移管理    │
                           └────────┬────────┘
                                    │
        ┌───────────────────────────┴───────────────────────────┐
        │                      VIP: 10.10.1.100                 │
        │                    (SIP/ESL统一入口)                   │
        └───────────────────────────┬───────────────────────────┘
                                    │
              ┌─────────────────────┴─────────────────────┐
              │                                           │
              ▼                                           ▼
   ┌────────────────────┐                    ┌────────────────────┐
   │   Master节点        │    心跳检测        │   Backup节点        │
   │   10.10.1.10       │◄──────────────────►│   10.10.1.11       │
   ├────────────────────┤                    ├────────────────────┤
   │  FreeSWITCH        │                    │  FreeSWITCH        │
   │  CTI中间件         │                    │  CTI中间件         │
   │  API服务           │                    │  API服务           │
   └────────────────────┘                    └────────────────────┘
              │                                           │
              │         共享存储 / 数据同步                │
              │                                           │
              └─────────────────────┬─────────────────────┘
                                    │
         ┌──────────────────────────┼──────────────────────────┐
         │                          │                          │
         ▼                          ▼                          ▼
┌─────────────────┐      ┌─────────────────┐      ┌─────────────────┐
│  MySQL 主从     │      │ Redis Sentinel  │      │    NAS存储      │
│  10.10.2.10/11  │      │  10.10.2.20-22  │      │   10.10.2.30    │
└─────────────────┘      └─────────────────┘      └─────────────────┘
```

### 5.2 Keepalived配置

**Master节点配置：**

```bash
# /etc/keepalived/keepalived.conf (Master)

global_defs {
    router_id FS_MASTER
}

# 检测FreeSWITCH状态的脚本
vrrp_script check_freeswitch {
    script "/etc/keepalived/check_freeswitch.sh"
    interval 2
    weight -20
    fall 3
    rise 2
}

vrrp_instance VI_FS {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    
    authentication {
        auth_type PASS
        auth_pass your_password
    }
    
    virtual_ipaddress {
        10.10.1.100/24 dev eth0
    }
    
    track_script {
        check_freeswitch
    }
    
    notify_master "/etc/keepalived/notify_master.sh"
    notify_backup "/etc/keepalived/notify_backup.sh"
    notify_fault "/etc/keepalived/notify_fault.sh"
}
```

**检测脚本：**

```bash
#!/bin/bash
# /etc/keepalived/check_freeswitch.sh

# 检测FreeSWITCH进程
if ! pgrep -x "freeswitch" > /dev/null; then
    exit 1
fi

# 检测ESL端口
if ! nc -z localhost 8021; then
    exit 1
fi

# 检测SIP端口
if ! nc -z localhost 5060; then
    exit 1
fi

# 执行简单API命令测试
result=$(fs_cli -x "status" 2>/dev/null)
if [[ $? -ne 0 ]]; then
    exit 1
fi

exit 0
```

**主节点切换通知脚本：**

```bash
#!/bin/bash
# /etc/keepalived/notify_master.sh

# 记录日志
echo "$(date): Becoming MASTER" >> /var/log/keepalived-state.log

# 启动FreeSWITCH（如果未运行）
systemctl start freeswitch

# 启动CTI服务
systemctl start callcenter-cti

# 发送告警通知
curl -X POST "http://your-alert-api/notify" \
    -d "message=FreeSWITCH切换到Master节点"
```

### 5.3 配置同步方案

```bash
#!/bin/bash
# sync_config.sh - 配置同步脚本

MASTER_HOST="10.10.1.10"
BACKUP_HOST="10.10.1.11"
FS_CONF_DIR="/usr/local/freeswitch/conf"

# 使用rsync同步配置
sync_config() {
    rsync -avz --delete \
        ${FS_CONF_DIR}/ \
        ${BACKUP_HOST}:${FS_CONF_DIR}/
    
    # 通知备机重载配置
    ssh ${BACKUP_HOST} "fs_cli -x 'reloadxml'"
}

# 监控配置变化
inotifywait -m -r -e modify,create,delete ${FS_CONF_DIR} | while read path action file; do
    echo "配置变化: $path$file ($action)"
    sync_config
done
```

### 5.4 FreeSWITCH集群配置

对于需要更高并发的场景，可以部署FreeSWITCH集群：

```xml
<!-- conf/autoload_configs/sofia.conf.xml -->
<configuration name="sofia.conf">
  <global_settings>
    <!-- 启用分布式设备状态 -->
    <param name="debug-presence" value="0"/>
  </global_settings>
  
  <profiles>
    <profile name="internal">
      <!-- ... -->
      
      <!-- 分布式配置 -->
      <gateways>
        <!-- 集群内其他节点 -->
        <gateway name="cluster_node_2">
          <param name="realm" value="10.10.1.12"/>
          <param name="register" value="false"/>
        </gateway>
      </gateways>
    </profile>
  </profiles>
</configuration>
```

---

## 第六部分 性能优化指南

### 6.1 FreeSWITCH优化

**系统级优化：**

```bash
# /etc/security/limits.conf
freeswitch soft nofile 65536
freeswitch hard nofile 65536
freeswitch soft core unlimited
freeswitch hard core unlimited

# /etc/sysctl.conf
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.rmem_default = 1048576
net.core.wmem_default = 1048576
net.core.netdev_max_backlog = 30000
net.ipv4.tcp_max_syn_backlog = 10240
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
```

**FreeSWITCH配置优化：**

```xml
<!-- conf/autoload_configs/switch.conf.xml -->
<configuration name="switch.conf">
  <settings>
    <!-- 核心线程数（根据CPU核心数调整） -->
    <param name="min-idle-cpu" value="25"/>
    <param name="max-sessions" value="2000"/>
    <param name="sessions-per-second" value="100"/>
    
    <!-- 日志级别（生产环境降低日志级别） -->
    <param name="loglevel" value="warning"/>
    
    <!-- RTP端口范围 -->
    <param name="rtp-start-port" value="16384"/>
    <param name="rtp-end-port" value="32768"/>
  </settings>
</configuration>
```

### 6.2 CTI中间件优化

```java
// 连接池配置
@Configuration
public class ConnectionPoolConfig {
    
    @Bean
    public ThreadPoolTaskExecutor eslEventExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(8);
        executor.setMaxPoolSize(32);
        executor.setQueueCapacity(10000);
        executor.setThreadNamePrefix("esl-event-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
    
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useClusterServers()
            .addNodeAddress("redis://10.10.2.20:6379")
            .addNodeAddress("redis://10.10.2.21:6379")
            .addNodeAddress("redis://10.10.2.22:6379")
            .setMasterConnectionPoolSize(64)
            .setSlaveConnectionPoolSize(64);
        return Redisson.create(config);
    }
}
```

### 6.3 监控指标

```yaml
# prometheus配置
scrape_configs:
  - job_name: 'freeswitch'
    static_configs:
      - targets: ['10.10.1.10:9282', '10.10.1.11:9282']
    
  - job_name: 'callcenter-cti'
    static_configs:
      - targets: ['10.10.1.10:8080', '10.10.1.11:8080']
    metrics_path: '/actuator/prometheus'
```

**关键监控指标：**

| 指标 | 说明 | 阈值 |
|------|------|------|
| freeswitch_sessions_active | 当前活跃会话数 | <80%最大值 |
| freeswitch_sessions_per_second | 每秒新建会话数 | <100 |
| freeswitch_cpu_usage | CPU使用率 | <70% |
| freeswitch_memory_usage | 内存使用率 | <80% |
| esl_event_queue_size | 事件队列大小 | <5000 |
| acd_queue_wait_time | 平均等待时间 | <30s |
| agent_utilization | 坐席利用率 | 目标>70% |

---

## 总结

本文档详细介绍了FreeSWITCH的技术架构和ESL开发实践，涵盖了：

1. **核心概念**：Channel、Session、Event等基础概念
2. **模块详解**：mod_sofia、Dialplan、ESL等核心模块
3. **ESL开发**：Java和Go两种语言的开发示例
4. **场景实践**：ACD、预测式外呼、机器人外呼、班长监控
5. **高可用部署**：主备架构、Keepalived配置
6. **性能优化**：系统优化、应用优化、监控指标

建议开发团队在正式开发前：
- 搭建FreeSWITCH测试环境熟悉操作
- 仔细研读FreeSWITCH官方文档
- 从简单场景（如点击拨号）开始，逐步实现复杂功能
- 重视测试，特别是高可用切换测试和压力测试

如有任何问题，欢迎继续交流！

### USER
给出一个粗略的开发方案

### ASSISTANT
# 呼叫中心系统开发方案

---

**文档版本**：V1.0  
**编制日期**：2024年1月  
**项目周期**：4个月（17周）

---

## 一、开发方案概述

### 1.1 项目背景回顾

| 项目要素 | 内容 |
|---------|------|
| 业务范围 | 道路救援、家政服务、代驾服务 |
| 坐席规模 | 150坐席 |
| 日话务量 | 50,000通 |
| 技术路线 | 自研（基于FreeSWITCH） |
| 部署方式 | 私有化 + 主备高可用 |

### 1.2 开发目标

```
┌─────────────────────────────────────────────────────────────────────────┐
│                           系统能力目标                                   │
├─────────────────────────────────────────────────────────────────────────┤
│  ✓ 150坐席并发          ✓ 120路并发通话        ✓ 99.9%可用性           │
│  ✓ 7×24小时服务         ✓ 故障30秒内切换       ✓ API响应<200ms         │
└─────────────────────────────────────────────────────────────────────────┘
```

---

## 二、系统架构设计

### 2.1 整体架构

```
┌─────────────────────────────────────────────────────────────────────────┐
│                              外部系统                                    │
│         CRM系统              业务系统              阿里云语音            │
└──────────┬───────────────────────┬────────────────────┬─────────────────┘
           │                       │                    │
           │         REST API / WebSocket               │
           │                       │                    │
┌──────────▼───────────────────────▼────────────────────▼─────────────────┐
│                           CTI中间件层                                    │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐    │
│  │  API网关    │  │  坐席服务   │  │  呼叫服务   │  │  外呼引擎   │    │
│  │  (Gateway)  │  │  (Agent)    │  │  (Call)     │  │  (Dialer)   │    │
│  └─────────────┘  └─────────────┘  └─────────────┘  └─────────────┘    │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐    │
│  │  ACD服务    │  │  队列服务   │  │  监控服务   │  │  机器人服务  │    │
│  │  (ACD)      │  │  (Queue)    │  │  (Monitor)  │  │  (Robot)    │    │
│  └─────────────┘  └─────────────┘  └─────────────┘  └─────────────┘    │
│  ┌─────────────┐  ┌─────────────┐                                      │
│  │  ESL连接池  │  │  事件分发器 │                                      │
│  │  (ESL Pool) │  │  (Dispatcher)│                                     │
│  └──────┬──────┘  └──────┬──────┘                                      │
└─────────┼────────────────┼──────────────────────────────────────────────┘
          │                │
          │    ESL协议     │
          │                │
┌─────────▼────────────────▼──────────────────────────────────────────────┐
│                        FreeSWITCH 媒体层                                 │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐ │  │
│  │  │mod_sofia│  │mod_dptools│ │mod_record│ │mod_lua  │  │mod_event│ │  │
│  │  │ (SIP)   │  │(Dialplan)│  │ (录音)  │  │ (IVR)   │  │ (ESL)   │ │  │
│  │  └─────────┘  └─────────┘  └─────────┘  └─────────┘  └─────────┘ │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                           FreeSWITCH Core                               │
└─────────────────────────────────┬───────────────────────────────────────┘
                                  │
                           SIP Trunk                                  
                                  │
┌─────────────────────────────────▼───────────────────────────────────────┐
│                          运营商网络                                      │
│                    400号码 / SIP中继线路                                │
└─────────────────────────────────────────────────────────────────────────┘
```

### 2.2 技术选型

| 层级 | 组件 | 技术选型 | 说明 |
|------|------|---------|------|
| **媒体层** | 软交换 | FreeSWITCH 1.10.x | 开源、稳定、功能完善 |
| | SIP协议 | mod_sofia | FreeSWITCH内置 |
| | IVR脚本 | Lua | 灵活、性能好 |
| **中间件层** | 开发语言 | Java 17 + Spring Boot 3 | 团队熟悉、生态成熟 |
| | ESL客户端 | 自研（基于Netty） | 更好的连接管理 |
| | API框架 | Spring WebFlux | 响应式、高性能 |
| | WebSocket | Spring WebSocket | 事件实时推送 |
| **数据层** | 关系数据库 | MySQL 8.0 | 主从复制、读写分离 |
| | 缓存 | Redis 7.0 | Sentinel高可用 |
| | 消息队列 | RabbitMQ | 异步解耦 |
| | 文件存储 | MinIO / NAS | 录音存储 |
| **前端** | 软电话条 | Vue 3 + TypeScript | 组件化、类型安全 |
| | 监控大屏 | Vue 3 + ECharts | 数据可视化 |
| **运维** | 容器化 | Docker + Docker Compose | 简化部署 |
| | 监控 | Prometheus + Grafana | 指标监控 |
| | 日志 | ELK Stack | 日志收集分析 |
| **外部服务** | ASR/TTS | 阿里云智能语音 | 按需调用 |
| | NLU | 指定对话平台 | 意图识别 |

### 2.3 模块划分

```
┌─────────────────────────────────────────────────────────────────────────┐
│                           系统模块划分                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                    callcenter-platform                          │   │
│  │                       (父工程)                                   │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                  │                                      │
│       ┌──────────────────────────┼──────────────────────────┐          │
│       │              │           │           │              │          │
│       ▼              ▼           ▼           ▼              ▼          │
│  ┌─────────┐   ┌─────────┐ ┌─────────┐ ┌─────────┐   ┌─────────┐      │
│  │ common  │   │  esl    │ │  core   │ │   api   │   │  web    │      │
│  │ (公共)  │   │(ESL封装)│ │(核心业务)│ │(REST API)│   │(前端)   │      │
│  └─────────┘   └─────────┘ └─────────┘ └─────────┘   └─────────┘      │
│       │              │           │           │              │          │
│       └──────────────┴───────────┴───────────┴──────────────┘          │
│                                  │                                      │
│                                  ▼                                      │
│                          ┌─────────────┐                               │
│                          │   gateway   │                               │
│                          │  (启动入口)  │                               │
│                          └─────────────┘                               │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

**模块职责：**

| 模块 | 职责 | 主要内容 |
|------|------|---------|
| **common** | 公共组件 | 工具类、常量、异常、通用模型 |
| **esl** | ESL封装 | ESL连接管理、命令封装、事件解析 |
| **core** | 核心业务 | 坐席管理、呼叫控制、ACD、队列、外呼、录音 |
| **api** | REST接口 | 对外API、WebSocket推送 |
| **web** | 前端工程 | 软电话条、监控大屏 |
| **gateway** | 应用入口 | 配置、启动类 |

---

## 三、功能模块设计

### 3.1 模块清单与优先级

```
┌─────────────────────────────────────────────────────────────────────────┐
│                          功能模块优先级                                  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  P0 - 核心功能（必须完成）                                              │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ ☐ ESL连接管理      ☐ 坐席签入签出     ☐ 呼入呼出控制           │   │
│  │ ☐ ACD话务分配      ☐ 技能组管理       ☐ 排队等待               │   │
│  │ ☐ 全程录音         ☐ 软电话条         ☐ API接口               │   │
│  │ ☐ 事件推送         ☐ CRM集成          ☐ 基础IVR               │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  P1 - 重要功能（应该完成）                                              │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ ☐ 预测式外呼       ☐ 机器人外呼       ☐ 语音识别IVR            │   │
│  │ ☐ 呼入机器人       ☐ 班长监控         ☐ 实时监控大屏           │   │
│  │ ☐ 溢出策略         ☐ 主备高可用                                │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  P2 - 增强功能（尽量完成）                                              │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ ☐ VIP优先          ☐ 黑名单           ☐ 满意度调查             │   │
│  │ ☐ 多轮对话         ☐ 话务预测         ☐ 密语功能               │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

### 3.2 核心模块设计

#### 3.2.1 ESL模块

```
┌─────────────────────────────────────────────────────────────────────────┐
│                          ESL模块设计                                     │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌───────────────────────────────────────────────────────────────────┐ │
│  │                      EslConnectionManager                         │ │
│  │                         (连接管理器)                               │ │
│  ├───────────────────────────────────────────────────────────────────┤ │
│  │  - connect()              建立ESL连接                             │ │
│  │  - disconnect()           断开连接                                │ │
│  │  - reconnect()            重连机制                                │ │
│  │  - heartbeat()            心跳检测                                │ │
│  │  - getConnection()        获取连接                                │ │
│  └───────────────────────────────────────────────────────────────────┘ │
│                                  │                                      │
│                                  ▼                                      │
│  ┌───────────────────────────────────────────────────────────────────┐ │
│  │                      EslCommandExecutor                           │ │
│  │                         (命令执行器)                               │ │
│  ├───────────────────────────────────────────────────────────────────┤ │
│  │  - api(cmd, args)         同步API命令                             │ │
│  │  - bgapi(cmd, args)       异步API命令                             │ │
│  │  - sendMsg(uuid, app)     发送消息                                │ │
│  │  - originate(...)         发起呼叫                                │ │
│  │  - bridge(uuid1, uuid2)   桥接通道                                │ │
│  │  - hangup(uuid)           挂断通话                                │ │
│  │  - transfer(uuid, dest)   转接                                    │ │
│  │  - hold/unhold(uuid)      保持/取回                               │ │
│  └───────────────────────────────────────────────────────────────────┘ │
│                                  │                                      │
│                                  ▼                                      │
│  ┌───────────────────────────────────────────────────────────────────┐ │
│  │                      EslEventDispatcher                           │ │
│  │                         (事件分发器)                               │ │
│  ├───────────────────────────────────────────────────────────────────┤ │
│  │  - subscribe(events)      订阅事件                                │ │
│  │  - addHandler(handler)    注册处理器                              │ │
│  │  - dispatch(event)        分发事件                                │ │
│  │  - filter(uuid)           过滤事件                                │ │
│  └───────────────────────────────────────────────────────────────────┘ │
│                                                                         │
│  关键事件处理：                                                         │
│  ┌────────────────┬────────────────────────────────────────────────┐  │
│  │ CHANNEL_CREATE │ → 创建呼叫记录，呼入触发ACD排队                │  │
│  │ CHANNEL_ANSWER │ → 更新状态为已接听，开始计时                   │  │
│  │ CHANNEL_BRIDGE │ → 更新状态为桥接，记录桥接信息                 │  │
│  │ CHANNEL_HANGUP │ → 更新坐席状态，生成话单                       │  │
│  │ DTMF           │ → IVR按键处理，满意度调查收集                  │  │
│  └────────────────┴────────────────────────────────────────────────┘  │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

#### 3.2.2 坐席管理模块

```
┌─────────────────────────────────────────────────────────────────────────┐
│                         坐席管理模块设计                                 │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  坐席状态机：                                                           │
│  ┌───────────────────────────────────────────────────────────────────┐ │
│  │                                                                   │ │
│  │              ┌─────────┐                                         │ │
│  │              │ OFFLINE │ ◄────────────────────────┐              │ │
│  │              └────┬────┘                          │              │ │
│  │                   │ signIn                        │ signOut      │ │
│  │                   ▼                               │              │ │
│  │              ┌─────────┐      ready          ┌────┴────┐         │ │
│  │         ┌───►│NOT_READY│─────────────────────►│  READY  │◄───┐   │ │
│  │         │    └─────────┘                     └────┬────┘    │   │ │
│  │         │         ▲                               │         │   │ │
│  │  wrapUp │         │ notReady                      │ assign  │   │ │
│  │  timeout│         │                               ▼         │   │ │
│  │         │    ┌─────────┐                     ┌─────────┐    │   │ │
│  │         │    │ PAUSED  │                     │ RINGING │    │   │ │
│  │         │    │ (小休)  │                     └────┬────┘    │   │ │
│  │         │    └─────────┘                          │         │   │ │
│  │         │                                         │ answer  │   │ │
│  │         │                                         ▼         │   │ │
│  │         │                                    ┌─────────┐    │   │ │
│  │         │                                    │  BUSY   │    │   │ │
│  │         │                                    │ (通话中) │    │   │ │
│  │         │                                    └────┬────┘    │   │ │
│  │         │                                         │         │   │ │
│  │         │                                         │ hangup  │   │ │
│  │         │                                         ▼         │   │ │
│  │         │                                    ┌─────────┐    │   │ │
│  │         └────────────────────────────────────│ WRAP_UP │────┘   │ │
│  │                                              │ (后处理) │        │ │
│  │                                              └─────────┘        │ │
│  │                                                                   │ │
│  └───────────────────────────────────────────────────────────────────┘ │
│                                                                         │
│  数据模型：                                                             │
│  ┌───────────────────────────────────────────────────────────────────┐ │
│  │  Agent {                                                          │ │
│  │      id: String              // 坐席ID                            │ │
│  │      name: String            // 坐席姓名                          │ │
│  │      extension: String       // 分机号                            │ │
│  │      skillGroups: List       // 技能组列表                        │ │
│  │      state: AgentState       // 当前状态                          │ │
│  │      stateTime: DateTime     // 状态开始时间                      │ │
│  │      currentCallUuid: String // 当前通话UUID                      │ │
│  │      signInTime: DateTime    // 签入时间                          │ │
│  │  }                                                                │ │
│  └───────────────────────────────────────────────────────────────────┘ │
│                                                                         │
│  存储设计（Redis）：                                                    │
│  ┌───────────────────────────────────────────────────────────────────┐ │
│  │  agent:{agentId}             → Hash    坐席详细信息               │ │
│  │  agents:online               → Set     在线坐席ID集合             │ │
│  │  skillgroup:{name}:agents    → Set     技能组坐席集合             │ │
│  │  agent:{agentId}:idle_time   → String  空闲开始时间               │ │
│  └───────────────────────────────────────────────────────────────────┘ │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

#### 3.2.3 ACD模块

```
┌─────────────────────────────────────────────────────────────────────────┐
│                           ACD模块设计                                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  分配流程：                                                             │
│  ┌───────────────────────────────────────────────────────────────────┐ │
│  │                                                                   │ │
│  │   来电 ──► IVR ──► 确定技能组 ──► 入队 ──► 分配 ──► 振铃 ──► 接听 │ │
│  │                                     │                             │ │
│  │                                     ▼                             │ │
│  │                               ┌──────────┐                        │ │
│  │                               │ 排队等待  │                        │ │
│  │                               │ 播放音乐  │                        │ │
│  │                               └──────────┘                        │ │
│  │                                                                   │ │
│  └───────────────────────────────────────────────────────────────────┘ │
│                                                                         │
│  分配策略（按优先级）：                                                 │
│  ┌───────────────────────────────────────────────────────────────────┐ │
│  │                                                                   │ │
│  │  1. 历史服务优先                                                  │ │
│  │     └─► 查找该客户上次服务的坐席，若空闲则分配                    │ │
│  │                                                                   │ │
│  │  2. 最长空闲优先                                                  │ │
│  │     └─► 选择空闲时间最长的坐席                                    │ │
│  │                                                                   │ │
│  │  3. 轮询分配                                                      │ │
│  │     └─► 按顺序轮流分配                                            │ │
│  │                                                                   │ │
│  └───────────────────────────────────────────────────────────────────┘ │
│                                                                         │
│  溢出策略：                                                             │
│  ┌───────────────────────────────────────────────────────────────────┐ │
│  │                                                                   │ │
│  │  条件：技能组无空闲坐席 + 等待时间 > 阈值（如60秒）               │ │
│  │                                                                   │ │
│  │  溢出配置：                                                       │ │
│  │  ┌─────────────┬─────────────┬───────────┐                       │ │
│  │  │ 源技能组    │ 目标技能组   │ 等待阈值  │                       │ │
│  │  ├─────────────┼─────────────┼───────────┤                       │ │
│  │  │ rescue      │ default     │ 60秒      │                       │ │
│  │  │ housekeeping│ default     │ 90秒      │                       │ │
│  │  │ driver      │ default     │ 60秒      │                       │ │
│  │  └─────────────┴─────────────┴───────────┘                       │ │
│  │                                                                   │ │
│  └───────────────────────────────────────────────────────────────────┘ │
│                                                                         │
│  队列数据结构（Redis Sorted Set）：                                     │
│  ┌───────────────────────────────────────────────────────────────────┐ │
│  │  queue:{skillGroup}                                               │ │
│  │  ┌──────────────────────────────────────────────────────────────┐│ │
│  │  │  Score = 优先级 × 10^9 + 入队时间戳                          ││ │
│  │  │  Member = QueueItem(callUuid, callerNumber, enqueueTime...)  ││ │
│  │  └──────────────────────────────────────────────────────────────┘│ │
│  │                                                                   │ │
│  │  优先级越小越优先，同优先级按入队时间排序                         │ │
│  └───────────────────────────────────────────────────────────────────┘ │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

#### 3.2.4 呼叫控制模块

```
┌─────────────────────────────────────────────────────────────────────────┐
│                         呼叫控制模块设计                                 │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  呼叫状态机：                                                           │
│  ┌───────────────────────────────────────────────────────────────────┐ │
│  │                                                                   │ │
│  │    ┌─────────┐     ┌─────────┐     ┌─────────┐     ┌─────────┐  │ │
│  │    │ CREATED │────►│ RINGING │────►│ ANSWERED│────►│ BRIDGED │  │ │
│  │    └─────────┘     └────┬────┘     └────┬────┘     └────┬────┘  │ │
│  │                         │              │               │        │ │
│  │                         │              │               │        │ │
│  │                         │              │               ▼        │ │
│  │                         │              │          ┌─────────┐   │ │
│  │                         │              │          │  HELD   │   │ │
│  │                         │              │          └────┬────┘   │ │
│  │                         │              │               │        │ │
│  │                         ▼              ▼               ▼        │ │
│  │                    ┌────────────────────────────────────────┐   │ │
│  │                    │              HANGUP                    │   │ │
│  │                    └────────────────────────────────────────┘   │ │
│  │                                                                   │ │
│  └───────────────────────────────────────────────────────────────────┘ │
│                                                                         │
│  核心操作：                                                             │
│  ┌───────────────────────────────────────────────────────────────────┐ │
│  │                                                                   │ │
│  │  点击外呼：                                                       │ │
│  │  ┌─────────────────────────────────────────────────────────────┐ │ │
│  │  │ 1. 检查坐席状态（必须READY）                                │ │ │
│  │  │ 2. 更新坐席状态为BUSY                                       │ │ │
│  │  │ 3. originate呼叫坐席分机                                    │ │ │
│  │  │ 4. 坐席接听后bridge呼叫客户                                 │ │ │
│  │  │ 5. 开始录音                                                 │ │ │
│  │  └─────────────────────────────────────────────────────────────┘ │ │
│  │                                                                   │ │
│  │  转接：                                                           │ │
│  │  ┌─────────────────────────────────────────────────────────────┐ │ │
│  │  │ 盲转：uuid_transfer直接转接                                 │ │ │
│  │  │ 咨询转：先hold，originate咨询方，确认后bridge               │ │ │
│  │  └─────────────────────────────────────────────────────────────┘ │ │
│  │                                                                   │ │
│  │  三方通话：                                                       │ │
│  │  ┌─────────────────────────────────────────────────────────────┐ │ │
│  │  │ 1. 创建会议室 conference                                    │ │ │
│  │  │ 2. 将原通话双方加入会议                                     │ │ │
│  │  │ 3. originate第三方加入会议                                  │ │ │
│  │  └─────────────────────────────────────────────────────────────┘ │ │
│  │                                                                   │ │
│  └───────────────────────────────────────────────────────────────────┘ │
│                                                                         │
│  数据模型：                                                             │
│  ┌───────────────────────────────────────────────────────────────────┐ │
│  │  Call {                                                           │ │
│  │      uuid: String             // 呼叫唯一标识                     │ │
│  │      direction: Enum          // INBOUND/OUTBOUND                 │ │
│  │      callerNumber: String     // 主叫号码                         │ │
│  │      calleeNumber: String     // 被叫号码                         │ │
│  │      state: CallState         // 呼叫状态                         │ │
│  │      agentId: String          // 服务坐席                         │ │
│  │      skillGroup: String       // 技能组                           │ │
│  │      createTime: DateTime     // 创建时间                         │ │
│  │      answerTime: DateTime     // 接听时间                         │ │
│  │      hangupTime: DateTime     // 挂断时间                         │ │
│  │      duration: Long           // 通话时长(秒)                     │ │
│  │      recordingPath: String    // 录音路径                         │ │
│  │      hangupCause: String      // 挂断原因                         │ │
│  │  }                                                                │ │
│  └───────────────────────────────────────────────────────────────────┘ │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

#### 3.2.5 外呼模块

```
┌─────────────────────────────────────────────────────────────────────────┐
│                          外呼模块设计                                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  预测式外呼流程：                                                       │
│  ┌───────────────────────────────────────────────────────────────────┐ │
│  │                                                                   │ │
│  │    业务系统 ──► 创建任务 ──► 导入号码 ──► 启动任务                │ │
│  │                                              │                    │ │
│  │                                              ▼                    │ │
│  │                                     ┌───────────────┐            │ │
│  │                                     │   外呼循环    │            │ │
│  │                                     └───────┬───────┘            │ │
│  │                                             │                    │ │
│  │         ┌───────────────────────────────────┴───────────────┐   │ │
│  │         │                                                   │   │ │
│  │         ▼                                                   │   │ │
│  │  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐     │   │ │
│  │  │ 计算拨打数  │───►│  批量拨打   │───►│ 接通转坐席  │     │   │ │
│  │  │(预测算法)   │    │             │    │             │     │   │ │
│  │  └─────────────┘    └─────────────┘    └─────────────┘     │   │ │
│  │         ▲                                      │            │   │ │
│  │         │                                      │            │   │ │
│  │         └──────────────────────────────────────┘            │   │ │
│  │                        循环                                  │   │ │
│  │                                                              │   │ │
│  └───────────────────────────────────────────────────────────────────┘ │
│                                                                         │
│  预测算法：                                                             │
│  ┌───────────────────────────────────────────────────────────────────┐ │
│  │                                                                   │ │
│  │  目标：让坐席接听时刚好有客户接通，减少等待                       │ │
│  │                                                                   │ │
│  │  预测拨打数 = 空闲坐席数 / 历史接通率 × 超拨系数                  │ │
│  │                                                                   │ │
│  │  示例：空闲5人，接通率30%，超拨系数1.2                            │ │
│  │  拨打数 = 5 / 0.3 × 1.2 ≈ 20                                      │ │
│  │                                                                   │ │
│  │  动态调整：根据实时接通率动态调整拨打速度                         │ │
│  │                                                                   │ │
│  └───────────────────────────────────────────────────────────────────┘ │
│                                                                         │
│  机器人外呼流程：                                                       │
│  ┌───────────────────────────────────────────────────────────────────┐ │
│  │                                                                   │ │
│  │  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐   │ │
│  │  │ 创建任务 │───►│ 生成TTS  │───►│ 批量拨打 │───►│ 播放语音 │   │ │
│  │  │ (含话术) │    │ 音频文件 │    │          │    │          │   │ │
│  │  └──────────┘    └──────────┘    └──────────┘    └────┬─────┘   │ │
│  │                                                       │         │ │
│  │                                           ┌───────────┴───────┐ │ │
│  │                                           │                   │ │ │
│  │                                           ▼                   ▼ │ │
│  │                                    ┌──────────┐       ┌──────────┐│
│  │                                    │ 纯播报   │       │ 收集按键 ││
│  │                                    │ 挂断     │       │ 回传结果 ││
│  │                                    └──────────┘       └──────────┘│
│  │                                                                   │ │
│  └───────────────────────────────────────────────────────────────────┘ │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

### 3.3 接口设计

#### 3.3.1 API接口清单

```
┌─────────────────────────────────────────────────────────────────────────┐
│                          REST API 设计                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  坐席接口：/api/v1/agent                                                │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ POST   /signin              坐席签入                            │   │
│  │ POST   /signout             坐席签出                            │   │
│  │ POST   /ready               置闲                                │   │
│  │ POST   /not-ready           置忙                                │   │
│  │ POST   /pause               小休                                │   │
│  │ GET    /{agentId}/status    查询坐席状态                        │   │
│  │ GET    /list                查询坐席列表                        │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  呼叫接口：/api/v1/call                                                 │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ POST   /originate           发起外呼                            │   │
│  │ POST   /{uuid}/answer       接听                                │   │
│  │ POST   /{uuid}/hangup       挂断                                │   │
│  │ POST   /{uuid}/hold         保持                                │   │
│  │ POST   /{uuid}/unhold       取回                                │   │
│  │ POST   /{uuid}/mute         静音                                │   │
│  │ POST   /{uuid}/unmute       取消静音                            │   │
│  │ POST   /{uuid}/transfer     转接                                │   │
│  │ POST   /{uuid}/conference   三方通话                            │   │
│  │ GET    /{uuid}              查询呼叫信息                        │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  外呼任务接口：/api/v1/task                                             │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ POST   /predictive          创建预测式外呼任务                  │   │
│  │ POST   /robot               创建机器人外呼任务                  │   │
│  │ POST   /{taskId}/start      启动任务                            │   │
│  │ POST   /{taskId}/pause      暂停任务                            │   │
│  │ POST   /{taskId}/resume     恢复任务                            │   │
│  │ POST   /{taskId}/stop       停止任务                            │   │
│  │ GET    /{taskId}/status     查询任务状态                        │   │
│  │ GET    /{taskId}/result     查询任务结果                        │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  监控接口：/api/v1/monitor                                              │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ POST   /spy                 监听                                │   │
│  │ POST   /barge               强插                                │   │
│  │ POST   /hangup              强拆                                │   │
│  │ GET    /realtime            实时统计数据                        │   │
│  │ GET    /queue/{skillGroup}  队列状态                            │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  录音接口：/api/v1/recording                                            │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ GET    /list                录音列表查询                        │   │
│  │ GET    /{uuid}/url          获取录音下载地址                    │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

#### 3.3.2 WebSocket事件推送

```
┌─────────────────────────────────────────────────────────────────────────┐
│                       WebSocket 事件设计                                 │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  连接地址：ws://host:port/ws/events?token={token}                       │
│                                                                         │
│  事件格式：                                                             │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  {                                                              │   │
│  │    "eventType": "CALL_RINGING",                                 │   │
│  │    "timestamp": 1704067200000,                                  │   │
│  │    "data": {                                                    │   │
│  │      "uuid": "xxx",                                             │   │
│  │      "callerNumber": "13800138000",                             │   │
│  │      "calleeNumber": "4001234567",                              │   │
│  │      ...                                                        │   │
│  │    }                                                            │   │
│  │  }                                                              │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  事件类型：                                                             │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ 呼叫事件                                                        │   │
│  │ ├── CALL_RINGING          来电振铃                             │   │
│  │ ├── CALL_ANSWERED         通话接听                             │   │
│  │ ├── CALL_BRIDGED          双方接通                             │   │
│  │ ├── CALL_HANGUP           通话挂断                             │   │
│  │ ├── CALL_TRANSFER         通话转接                             │   │
│  │ └── CALL_HOLD             通话保持                             │   │
│  │                                                                 │   │
│  │ 坐席事件                                                        │   │
│  │ ├── AGENT_STATE_CHANGE    坐席状态变更                         │   │
│  │ ├── AGENT_SIGNIN          坐席签入                             │   │
│  │ └── AGENT_SIGNOUT         坐席签出                             │   │
│  │                                                                 │   │
│  │ 话单事件                                                        │   │
│  │ └── CDR_GENERATED         话单生成                             │   │
│  │                                                                 │   │
│  │ DTMF事件                                                        │   │
│  │ └── DTMF_RECEIVED         收到按键                             │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

### 3.4 数据库设计

```
┌─────────────────────────────────────────────────────────────────────────┐
│                          数据库设计                                      │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  cc_agent (坐席表)                                                      │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ id              BIGINT          PRIMARY KEY                     │   │
│  │ agent_id        VARCHAR(32)     坐席工号                        │   │
│  │ agent_name      VARCHAR(64)     坐席姓名                        │   │
│  │ extension       VARCHAR(16)     分机号                          │   │
│  │ password        VARCHAR(128)    密码                            │   │
│  │ skill_groups    VARCHAR(256)    技能组(逗号分隔)                │   │
│  │ status          TINYINT         状态(0禁用1启用)                │   │
│  │ create_time     DATETIME        创建时间                        │   │
│  │ update_time     DATETIME        更新时间                        │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  cc_skill_group (技能组表)                                              │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ id              BIGINT          PRIMARY KEY                     │   │
│  │ group_code      VARCHAR(32)     技能组编码                      │   │
│  │ group_name      VARCHAR(64)     技能组名称                      │   │
│  │ overflow_group  VARCHAR(32)     溢出技能组                      │   │
│  │ overflow_seconds INT            溢出等待秒数                    │   │
│  │ status          TINYINT         状态                            │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  cc_cdr (话单表)                                                        │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ id              BIGINT          PRIMARY KEY                     │   │
│  │ uuid            VARCHAR(64)     呼叫UUID                        │   │
│  │ direction       VARCHAR(16)     呼叫方向                        │   │
│  │ caller_number   VARCHAR(32)     主叫号码                        │   │
│  │ callee_number   VARCHAR(32)     被叫号码                        │   │
│  │ agent_id        VARCHAR(32)     坐席ID                          │   │
│  │ skill_group     VARCHAR(32)     技能组                          │   │
│  │ queue_time      INT             排队时长(秒)                    │   │
│  │ ring_time       INT             振铃时长(秒)                    │   │
│  │ talk_time       INT             通话时长(秒)                    │   │
│  │ start_time      DATETIME        开始时间                        │   │
│  │ answer_time     DATETIME        接听时间                        │   │
│  │ end_time        DATETIME        结束时间                        │   │
│  │ hangup_cause    VARCHAR(32)     挂断原因                        │   │
│  │ recording_path  VARCHAR(256)    录音路径                        │   │
│  │ business_type   VARCHAR(32)     业务类型                        │   │
│  │ create_time     DATETIME        创建时间                        │   │
│  │ INDEX idx_start_time (start_time)                               │   │
│  │ INDEX idx_agent (agent_id, start_time)                          │   │
│  │ INDEX idx_caller (caller_number, start_time)                    │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  cc_dial_task (外呼任务表)                                              │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ id              BIGINT          PRIMARY KEY                     │   │
│  │ task_id         VARCHAR(64)     任务ID                          │   │
│  │ task_name       VARCHAR(128)    任务名称                        │   │
│  │ task_type       VARCHAR(16)     类型(PREDICTIVE/ROBOT)          │   │
│  │ skill_group     VARCHAR(32)     技能组                          │   │
│  │ caller_id       VARCHAR(32)     外显号码                        │   │
│  │ total_count     INT             总号码数                        │   │
│  │ dialed_count    INT             已拨打数                        │   │
│  │ connected_count INT             接通数                          │   │
│  │ state           VARCHAR(16)     状态                            │   │
│  │ start_time      DATETIME        开始时间                        │   │
│  │ end_time        DATETIME        结束时间                        │   │
│  │ create_time     DATETIME        创建时间                        │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  cc_dial_detail (外呼明细表)                                            │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ id              BIGINT          PRIMARY KEY                     │   │
│  │ task_id         VARCHAR(64)     任务ID                          │   │
│  │ phone           VARCHAR(32)     号码                            │   │
│  │ call_uuid       VARCHAR(64)     呼叫UUID                        │   │
│  │ status          VARCHAR(16)     状态                            │   │
│  │ dial_time       DATETIME        拨打时间                        │   │
│  │ answer_time     DATETIME        接听时间                        │   │
│  │ hangup_time     DATETIME        挂断时间                        │   │
│  │ dtmf_result     VARCHAR(16)     按键结果                        │   │
│  │ retry_count     INT             重试次数                        │   │
│  │ INDEX idx_task (task_id)                                        │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

---

## 四、开发计划

### 4.1 阶段划分

```
┌─────────────────────────────────────────────────────────────────────────┐
│                          开发阶段划分（17周）                            │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ 第一阶段：基础环境与核心框架（Week 1-4）                        │   │
│  │                                                                 │   │
│  │ Week 1-2: 环境搭建 + 技术预研                                   │   │
│  │ Week 3-4: ESL模块 + 基础框架                                    │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                            ↓                                            │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ 第二阶段：核心功能开发（Week 5-10）                             │   │
│  │                                                                 │   │
│  │ Week 5-6: 坐席管理 + 呼叫控制                                   │   │
│  │ Week 7-8: ACD分配 + 排队等待                                    │   │
│  │ Week 9-10: 录音 + 软电话条 + CRM集成                            │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                            ↓                                            │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ 第三阶段：高级功能开发（Week 11-14）                            │   │
│  │                                                                 │   │
│  │ Week 11-12: 预测式外呼 + 机器人外呼                             │   │
│  │ Week 13: IVR + 呼入机器人                                       │   │
│  │ Week 14: 监控大屏 + 班长监控                                    │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                            ↓                                            │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ 第四阶段：集成测试与上线（Week 15-17）                          │   │
│  │                                                                 │   │
│  │ Week 15: 集成联调 + 高可用部署                                  │   │
│  │ Week 16: 测试 + Bug修复                                         │   │
│  │ Week 17: 试运行 + 正式上线                                      │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

### 4.2 详细开发计划

```
┌─────────────────────────────────────────────────────────────────────────┐
│                            详细开发计划                                  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ═══════════════════════════════════════════════════════════════════   │
│  第一阶段：基础环境与核心框架（Week 1-4）                               │
│  ═══════════════════════════════════════════════════════════════════   │
│                                                                         │
│  Week 1: 环境准备                                                       │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ □ 服务器环境准备（开发、测试环境）                              │   │
│  │ □ FreeSWITCH安装配置                                            │   │
│  │ □ 开发环境搭建（IDE、Git、Maven）                               │   │
│  │ □ MySQL、Redis环境搭建                                          │   │
│  │ □ 测试SIP话机注册与基础通话                                     │   │
│  │                                                                 │   │
│  │ 交付物：环境部署文档、FreeSWITCH基础配置                        │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  Week 2: 技术预研                                                       │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ □ ESL协议学习与Demo开发                                         │   │
│  │ □ FreeSWITCH Dialplan配置学习                                   │   │
│  │ □ Lua IVR脚本编写测试                                           │   │
│  │ □ 项目工程结构搭建                                              │   │
│  │ □ 技术方案评审                                                  │   │
│  │                                                                 │   │
│  │ 交付物：技术预研报告、Demo代码                                  │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  Week 3-4: ESL模块开发                                                  │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ □ ESL连接管理（连接池、心跳、重连）                             │   │
│  │ □ ESL命令封装（API、SendMsg）                                   │   │
│  │ □ 事件订阅与分发机制                                            │   │
│  │ □ 常用命令测试验证                                              │   │
│  │ □ 基础API框架搭建                                               │   │
│  │ □ WebSocket服务搭建                                             │   │
│  │                                                                 │   │
│  │ 交付物：ESL模块代码、单元测试                                   │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  ═══════════════════════════════════════════════════════════════════   │
│  第二阶段：核心功能开发（Week 5-10）                                    │
│  ═══════════════════════════════════════════════════════════════════   │
│                                                                         │
│  Week 5-6: 坐席管理 + 呼叫控制                                          │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ □ 坐席数据模型设计                                              │   │
│  │ □ 坐席签入/签出                                                 │   │
│  │ □ 坐席状态管理（置闲、置忙、小休）                              │   │
│  │ □ 技能组管理                                                    │   │
│  │ □ 点击外呼功能                                                  │   │
│  │ □ 接听/挂断/保持/取回                                           │   │
│  │ □ 转接（盲转、咨询转）                                          │   │
│  │ □ 坐席API接口                                                   │   │
│  │ □ 呼叫API接口                                                   │   │
│  │                                                                 │   │
│  │ 交付物：坐席模块、呼叫控制模块                                  │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  Week 7-8: ACD分配 + 排队等待                                           │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ □ 队列数据结构设计                                              │   │
│  │ □ 呼叫入队逻辑                                                  │   │
│  │ □ 历史服务优先策略                                              │   │
│  │ □ 最长空闲优先策略                                              │   │
│  │ □ 轮询分配策略                                                  │   │
│  │ □ 坐席选择与分配                                                │   │
│  │ □ 排队等待音乐                                                  │   │
│  │ □ 溢出策略实现                                                  │   │
│  │ □ 排队状态查询                                                  │   │
│  │                                                                 │   │
│  │ 交付物：ACD模块、队列管理模块                                   │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  Week 9-10: 录音 + 软电话条 + CRM集成                                   │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ □ 录音启动/停止控制                                             │   │
│  │ □ 录音文件存储管理                                              │   │
│  │ □ 录音查询与下载API                                             │   │
│  │ □ 软电话条前端开发（Vue组件）                                   │   │
│  │ □ WebSocket事件接收与处理                                       │   │
│  │ □ 软电话条UI（状态、按钮、来电显示）                            │   │
│  │ □ CRM弹屏集成（事件推送）                                       │   │
│  │ □ 话单推送至CRM                                                 │   │
│  │ □ 联调测试                                                      │   │
│  │                                                                 │   │
│  │ 交付物：录音模块、软电话条组件、CRM集成文档                     │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  ═══════════════════════════════════════════════════════════════════   │
│  第三阶段：高级功能开发（Week 11-14）                                   │
│  ═══════════════════════════════════════════════════════════════════   │
│                                                                         │
│  Week 11-12: 预测式外呼 + 机器人外呼                                    │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ □ 外呼任务管理（创建、启动、暂停、停止）                        │   │
│  │ □ 号码导入与管理                                                │   │
│  │ □ 预测式拨号算法实现                                            │   │
│  │ □ 接通后转坐席逻辑                                              │   │
│  │ □ 外呼统计（接通率、坐席利用率）                                │   │
│  │ □ 阿里云TTS集成                                                 │   │
│  │ □ 机器人外呼流程（播报、按键收集）                              │   │
│  │ □ 外呼结果回传                                                  │   │
│  │ □ 任务API接口                                                   │   │
│  │                                                                 │   │
│  │ 交付物：外呼引擎、机器人外呼模块                                │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  Week 13: IVR + 呼入机器人                                              │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ □ IVR Dialplan配置（多业务线）                                  │   │
│  │ □ 阿里云ASR集成                                                 │   │
│  │ □ 语音识别导航实现                                              │   │
│  │ □ NLU对接（意图识别）                                           │   │
│  │ □ FAQ问答流程                                                   │   │
│  │ □ 转人工逻辑                                                    │   │
│  │ □ Lua脚本开发                                                   │   │
│  │                                                                 │   │
│  │ 交付物：IVR配置、呼入机器人模块                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  Week 14: 监控大屏 + 班长监控                                           │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ □ 实时数据统计API                                               │   │
│  │ □ 监控大屏前端开发                                              │   │
│  │ □ 坐席状态分布展示                                              │   │
│  │ □ 队列状态展示                                                  │   │
│  │ □ 话务趋势图表                                                  │   │
│  │ □ 监听功能实现                                                  │   │
│  │ □ 强插功能实现                                                  │   │
│  │ □ 强拆功能实现                                                  │   │
│  │                                                                 │   │
│  │ 交付物：监控大屏、班长监控功能                                  │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  ═══════════════════════════════════════════════════════════════════   │
│  第四阶段：集成测试与上线（Week 15-17）                                 │
│  ═══════════════════════════════════════════════════════════════════   │
│                                                                         │
│  Week 15: 集成联调 + 高可用部署                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ □ CRM系统全流程联调                                             │   │
│  │ □ 业务系统对接联调                                              │   │
│  │ □ 运营商线路对接                                                │   │
│  │ □ 主备FreeSWITCH部署                                            │   │
│  │ □ Keepalived配置                                                │   │
│  │ □ 故障切换测试                                                  │   │
│  │ □ 数据库主从配置                                                │   │
│  │ □ Redis Sentinel配置                                            │   │
│  │                                                                 │   │
│  │ 交付物：联调测试报告、高可用部署文档                            │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  Week 16: 测试 + Bug修复                                                │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ □ 功能测试（全流程）                                            │   │
│  │ □ 性能测试（150坐席并发）                                       │   │
│  │ □ 压力测试（峰值话务）                                          │   │
│  │ □ 故障恢复测试                                                  │   │
│  │ □ Bug修复                                                       │   │
│  │ □ 性能优化                                                      │   │
│  │ □ 上线检查清单确认                                              │   │
│  │                                                                 │   │
│  │ 交付物：测试报告、Bug清单、优化记录                             │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  Week 17: 试运行 + 正式上线                                             │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ □ 生产环境部署                                                  │   │
│  │ □ 数据初始化                                                    │   │
│  │ □ 小范围试运行（一条业务线）                                    │   │
│  │ □ 问题收集与修复                                                │   │
│  │ □ 全量上线                                                      │   │
│  │ □ 运维交接                                                      │   │
│  │ □ 用户培训                                                      │   │
│  │                                                                 │   │
│  │ 交付物：上线报告、运维手册、培训材料                            │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

### 4.3 甘特图

```
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                                     项目甘特图                                           │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                         │
│ 任务                      │ W1 │ W2 │ W3 │ W4 │ W5 │ W6 │ W7 │ W8 │ W9 │W10│W11│W12│W13│W14│W15│W16│W17│
│ ─────────────────────────│────│────│────│────│────│────│────│────│────│───│───│───│───│───│───│───│───│
│ 第一阶段                  │████│████│████│████│    │    │    │    │    │   │   │   │   │   │   │   │   │
│  环境搭建                 │████│    │    │    │    │    │    │    │    │   │   │   │   │   │   │   │   │
│  技术预研                 │    │████│    │    │    │    │    │    │    │   │   │   │   │   │   │   │   │
│  ESL模块开发              │    │    │████│████│    │    │    │    │    │   │   │   │   │   │   │   │   │
│ ─────────────────────────│────│────│────│────│────│────│────│────│────│───│───│───│───│───│───│───│───│
│ 第二阶段                  │    │    │    │    │████│████│████│████│████│███│   │   │   │   │   │   │   │
│  坐席管理                 │    │    │    │    │████│████│    │    │    │   │   │   │   │   │   │   │   │
│  呼叫控制                 │    │    │    │    │████│████│    │    │    │   │   │   │   │   │   │   │   │
│  ACD分配                  │    │    │    │    │    │    │████│████│    │   │   │   │   │   │   │   │   │
│  排队等待                 │    │    │    │    │    │    │████│████│    │   │   │   │   │   │   │   │   │
│  录音功能                 │    │    │    │    │    │    │    │    │████│   │   │   │   │   │   │   │   │
│  软电话条                 │    │    │    │    │    │    │    │    │████│███│   │   │   │   │   │   │   │
│  CRM集成                  │    │    │    │    │    │    │    │    │    │███│   │   │   │   │   │   │   │
│ ─────────────────────────│────│────│────│────│────│────│────│────│────│───│───│───│───│───│───│───│───│
│ 第三阶段                  │    │    │    │    │    │    │    │    │    │   │███│███│███│███│   │   │   │
│  预测式外呼               │    │    │    │    │    │    │    │    │    │   │███│███│   │   │   │   │   │
│  机器人外呼               │    │    │    │    │    │    │    │    │    │   │███│███│   │   │   │   │   │
│  IVR+呼入机器人           │    │    │    │    │    │    │    │    │    │   │   │   │███│   │   │   │   │
│  监控大屏                 │    │    │    │    │    │    │    │    │    │   │   │   │   │███│   │   │   │
│  班长监控                 │    │    │    │    │    │    │    │    │    │   │   │   │   │███│   │   │   │
│ ─────────────────────────│────│────│────│────│────│────│────│────│────│───│───│───│───│───│───│───│───│
│ 第四阶段                  │    │    │    │    │    │    │    │    │    │   │   │   │   │   │███│███│███│
│  集成联调                 │    │    │    │    │    │    │    │    │    │   │   │   │   │   │███│   │   │
│  高可用部署               │    │    │    │    │    │    │    │    │    │   │   │   │   │   │███│   │   │
│  测试+Bug修复             │    │    │    │    │    │    │    │    │    │   │   │   │   │   │   │███│   │
│  试运行                   │    │    │    │    │    │    │    │    │    │   │   │   │   │   │   │   │██ │
│  正式上线                 │    │    │    │    │    │    │    │    │    │   │   │   │   │   │   │   │ ██│
│ ─────────────────────────│────│────│────│────│────│────│────│────│────│───│───│───│───│───│───│───│───│
│ 里程碑                    │    │ M1 │    │ M2 │    │    │    │    │    │M3 │   │   │   │M4 │M5 │M6 │M7 │
│                          │    │    │    │    │    │    │    │    │    │   │   │   │   │   │   │   │   │
│ M1: 需求确认              │    │ ◆  │    │    │    │    │    │    │    │   │   │   │   │   │   │   │   │
│ M2: 环境就绪              │    │    │    │ ◆  │    │    │    │    │    │   │   │   │   │   │   │   │   │
│ M3: 核心功能完成          │    │    │    │    │    │    │    │    │    │ ◆ │   │   │   │   │   │   │   │
│ M4: 全功能完成            │    │    │    │    │    │    │    │    │    │   │   │   │   │ ◆ │   │   │   │
│ M5: 联调测试完成          │    │    │    │    │    │    │    │    │    │   │   │   │   │   │ ◆ │   │   │
│ M6: 试运行完成            │    │    │    │    │    │    │    │    │    │   │   │   │   │   │   │ ◆ │   │
│ M7: 正式上线              │    │    │    │    │    │    │    │    │    │   │   │   │   │   │   │   │ ◆ │
│                                                                                         │
└─────────────────────────────────────────────────────────────────────────────────────────┘
```

---

## 五、团队组织

### 5.1 团队配置

```
┌─────────────────────────────────────────────────────────────────────────┐
│                           团队组织架构                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│                         ┌─────────────┐                                │
│                         │  项目经理   │                                │
│                         │   (1人)     │                                │
│                         └──────┬──────┘                                │
│                                │                                        │
│          ┌─────────────────────┼─────────────────────┐                 │
│          │                     │                     │                 │
│          ▼                     ▼                     ▼                 │
│   ┌─────────────┐      ┌─────────────┐      ┌─────────────┐           │
│   │  开发组     │      │  测试组     │      │  运维组     │           │
│   │  (8-10人)   │      │  (1-2人)    │      │  (1人)      │           │
│   └─────────────┘      └─────────────┘      └─────────────┘           │
│          │                                                              │
│          ├── 架构师 (1人)                                              │
│          ├── 后端开发 (4-5人)                                          │
│          ├── 前端开发 (2人)                                            │
│          └── FreeSWITCH工程师 (1-2人)                                  │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

### 5.2 角色职责

| 角色 | 人数 | 职责 | 技能要求 |
|------|------|------|---------|
| **项目经理** | 1 | 项目管理、进度控制、风险管理 | 项目管理经验、沟通协调能力 |
| **架构师** | 1 | 架构设计、技术选型、核心代码 | FreeSWITCH、分布式系统 |
| **后端开发** | 4-5 | 业务模块开发、API开发 | Java、Spring Boot、Redis |
| **前端开发** | 2 | 软电话条、监控大屏 | Vue3、TypeScript、WebSocket |
| **FS工程师** | 1-2 | FreeSWITCH配置、Lua脚本 | FreeSWITCH、SIP协议、Lua |
| **测试** | 1-2 | 功能测试、性能测试 | 测试用例设计、性能测试工具 |
| **运维** | 1 | 环境部署、监控运维 | Linux、Docker、运维经验 |

### 5.3 人员分工

```
┌─────────────────────────────────────────────────────────────────────────┐
│                           开发任务分工                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  架构师                                                                 │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ • 整体架构设计                                                  │   │
│  │ • ESL模块核心代码                                               │   │
│  │ • ACD核心算法                                                   │   │
│  │ • 技术难点攻关                                                  │   │
│  │ • 代码Review                                                    │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  后端开发A (资深)                                                       │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ • 坐席管理模块                                                  │   │
│  │ • 呼叫控制模块                                                  │   │
│  │ • WebSocket推送服务                                             │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  后端开发B                                                              │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ • ACD分配模块                                                   │   │
│  │ • 队列管理模块                                                  │   │
│  │ • 溢出策略                                                      │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  后端开发C                                                              │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ • 预测式外呼模块                                                │   │
│  │ • 机器人外呼模块                                                │   │
│  │ • TTS集成                                                       │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  后端开发D                                                              │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ • 录音管理模块                                                  │   │
│  │ • 监控统计模块                                                  │   │
│  │ • 班长监控功能                                                  │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  后端开发E (可选)                                                       │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ • API接口开发                                                   │   │
│  │ • CRM集成对接                                                   │   │
│  │ • 单元测试                                                      │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  前端开发A                                                              │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ • 软电话条组件开发                                              │   │
│  │ • WebSocket客户端                                               │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  前端开发B                                                              │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ • 实时监控大屏                                                  │   │
│  │ • 数据可视化图表                                                │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  FreeSWITCH工程师                                                       │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ • FreeSWITCH配置优化                                            │   │
│  │ • Dialplan编写                                                  │   │
│  │ • Lua IVR脚本开发                                               │   │
│  │ • 线路对接                                                      │   │
│  │ • 高可用部署                                                    │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

---

## 六、风险控制

### 6.1 风险清单

```
┌─────────────────────────────────────────────────────────────────────────┐
│                            风险清单                                      │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ 风险1: FreeSWITCH技术门槛高                                     │   │
│  ├─────────────────────────────────────────────────────────────────┤   │
│  │ 等级: 高                                                        │   │
│  │ 影响: 开发进度延迟，功能实现困难                                │   │
│  │ 应对:                                                           │   │
│  │   • Week 1-2 集中学习FreeSWITCH和ESL                            │   │
│  │   • 考虑引入外部FreeSWITCH顾问                                  │   │
│  │   • 预留20%时间缓冲                                             │   │
│  │   • 简化复杂功能，优先实现核心流程                              │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ 风险2: 运营商线路对接周期长                                     │   │
│  ├─────────────────────────────────────────────────────────────────┤   │
│  │ 等级: 高                                                        │   │
│  │ 影响: 无法进行真实环境测试，上线延迟                            │   │
│  │ 应对:                                                           │   │
│  │   • 项目启动即开始申请线路                                      │   │
│  │   • 开发阶段使用模拟线路/测试线路                               │   │
│  │   • 准备备选运营商                                              │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ 风险3: CRM系统对接复杂                                          │   │
│  ├─────────────────────────────────────────────────────────────────┤   │
│  │ 等级: 中                                                        │   │
│  │ 影响: 集成联调时间延长                                          │   │
│  │ 应对:                                                           │   │
│  │   • 提前与CRM团队沟通，明确接口规范                             │   │
│  │   • 提供详细API文档和示例代码                                   │   │
│  │   • 预留充足联调时间                                            │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ 风险4: 并发性能不达标                                           │   │
│  ├─────────────────────────────────────────────────────────────────┤   │
│  │ 等级: 中                                                        │   │
│  │ 影响: 系统无法支撑业务量                                        │   │
│  │ 应对:                                                           │   │
│  │   • Week 16 进行完整压力测试                                    │   │
│  │   • 预先设计水平扩展方案                                        │   │
│  │   • 关键代码Review，避免性能瓶颈                                │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ 风险5: ASR/TTS识别效果不佳                                      │   │
│  ├─────────────────────────────────────────────────────────────────┤   │
│  │ 等级: 中                                                        │   │
│  │ 影响: 机器人/IVR体验差                                          │   │
│  │ 应对:                                                           │   │
│  │   • 提前进行ASR效果测试                                         │   │
│  │   • 准备热词优化方案                                            │   │
│  │   • 设计按键兜底方案                                            │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ 风险6: 核心人员离职                                             │   │
│  ├─────────────────────────────────────────────────────────────────┤   │
│  │ 等级: 中                                                        │   │
│  │ 影响: 知识断层，进度受阻                                        │   │
│  │ 应对:                                                           │   │
│  │   • 重要模块双人备份                                            │   │
│  │   • 代码规范、文档完善                                          │   │
│  │   • 定期代码Review和知识分享                                    │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

### 6.2 应急预案

```
┌─────────────────────────────────────────────────────────────────────────┐
│                           应急预案                                       │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  场景1: 开发进度严重滞后                                                │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ 触发条件: 核心功能延迟超过2周                                   │   │
│  │ 应对措施:                                                       │   │
│  │   • 评估是否可以简化功能范围                                    │   │
│  │   • 考虑增加外部资源支持                                        │   │
│  │   • 与业务方协商分阶段上线                                      │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  场景2: FreeSWITCH关键问题无法解决                                      │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ 触发条件: 技术问题阻塞超过1周                                   │   │
│  │ 应对措施:                                                       │   │
│  │   • 联系FreeSWITCH社区或付费支持                                │   │
│  │   • 寻求外部FreeSWITCH专家支援                                  │   │
│  │   • 评估替代方案可行性                                          │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  场景3: 上线后系统不稳定                                                │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │ 触发条件: 故障频发或性能严重不足                                │   │
│  │ 应对措施:                                                       │   │
│  │   • 启用回退方案（如有旧系统）                                  │   │
│  │   • 降级部分功能，保障核心业务                                  │   │
│  │   • 紧急扩容或优化                                              │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

---

## 七、交付物清单

```
┌─────────────────────────────────────────────────────────────────────────┐
│                           项目交付物清单                                 │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  阶段           │ 交付物                                  │ 负责人     │
│  ──────────────│───────────────────────────────────────│───────────│
│                 │                                        │           │
│  需求阶段       │ 需求规格说明书                        │ 产品       │
│                 │ 接口设计文档                          │ 架构师     │
│                 │ 数据库设计文档                        │ 架构师     │
│                 │                                        │           │
│  开发阶段       │ 源代码（Git仓库）                     │ 开发团队   │
│                 │ 单元测试代码                          │ 开发团队   │
│                 │ API接口文档                           │ 后端开发   │
│                 │ 前端组件文档                          │ 前端开发   │
│                 │                                        │           │
│  测试阶段       │ 测试用例文档                          │ 测试       │
│                 │ 测试报告                              │ 测试       │
│                 │ 性能测试报告                          │ 测试       │
│                 │                                        │           │
│  部署阶段       │ 部署手册                              │ 运维       │
│                 │ 运维手册                              │ 运维       │
│                 │ 高可用配置文档                        │ 运维       │
│                 │                                        │           │
│  上线阶段       │ 上线检查清单                          │ 项目经理   │
│                 │ 培训材料                              │ 产品       │
│                 │ 用户操作手册                          │ 产品       │
│                 │ 项目总结报告                          │ 项目经理   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

---

## 八、总结

### 8.1 方案要点总结

| 维度 | 要点 |
|------|------|
| **技术架构** | FreeSWITCH + Java中间件 + Vue前端，ESL控制模式 |
| **开发周期** | 4个月（17周），分4阶段 |
| **团队配置** | 11-14人（含项目经理、开发、测试、运维） |
| **核心功能** | 呼入呼出、ACD分配、预测式外呼、机器人、录音、监控 |
| **风险控制** | FreeSWITCH技术预研、线路提前申请、预留缓冲时间 |

### 8.2 关键成功因素

```
┌─────────────────────────────────────────────────────────────────────────┐
│                         关键成功因素                                     │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  1. 技术准备充分                                                        │
│     └─► Week 1-2 完成FreeSWITCH学习和技术预研                          │
│                                                                         │
│  2. 核心功能优先                                                        │
│     └─► 先完成P0功能，确保基础可用                                     │
│                                                                         │
│  3. 持续集成测试                                                        │
│     └─► 每完成一个模块立即测试，避免问题堆积                           │
│                                                                         │
│  4. CRM团队紧密配合                                                     │
│     └─► 提前沟通接口，并行开发                                         │
│                                                                         │
│  5. 线路提前就位                                                        │
│     └─► 项目启动即申请运营商线路                                       │
│                                                                         │
│  6. 分阶段上线                                                          │
│     └─► 先上线一条业务线验证，再全量推广                               │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

### 8.3 下一步行动

| 序号 | 行动项 | 负责人 | 完成时间 |
|------|-------|-------|---------|
| 1 | 确认团队人员到位 | 项目经理 | 项目启动前 |
| 2 | 采购/准备开发测试环境 | 运维 | Week 1 |
| 3 | 启动运营商线路申请 | 项目经理 | Week 1 |
| 4 | 组织FreeSWITCH技术培训 | 架构师 | Week 1-2 |
| 5 | 与CRM团队召开接口对接会议 | 架构师 | Week 2 |
| 6 | 完成技术方案评审 | 架构师 | Week 2 |

---

**文档结束**

如需进一步细化某个模块的设计或有其他问题，请随时告知！